Compare commits

...

17 Commits

Author SHA1 Message Date
Lxq
e304c012ce 业务逻辑修改 2026-02-08 17:02:10 +08:00
Lxq
e36faaf94f PLC设备管理 2026-02-02 13:48:55 +08:00
Lxq
0701e9b536 页面优化 2026-01-26 09:29:46 +08:00
Lxq
82764f06d1 PLC设备管理页面修改 2026-01-23 11:26:14 +08:00
Lxq
7e3ff083c7 登录注册管理 2026-01-19 16:15:02 +08:00
Lxq
d95092a4c9 完善 2026-01-19 13:41:14 +08:00
Lxq
36e0644b5d 样品信息管理 2026-01-14 14:04:08 +08:00
Lxq
7c2678429c 页面优化 2026-01-08 14:10:55 +08:00
Lxq
2c25ef613b 页眉修改 2026-01-08 13:05:59 +08:00
Lxq
3c5d854967 标准流程管理+流程创建 2026-01-08 11:52:11 +08:00
Lxq
5f6808b2a4 字典类型管理+字典数据管理 2026-01-05 13:07:30 +08:00
Lxq
bdd50a498b 标准流程管理+流程创建 2026-01-04 17:52:10 +08:00
6689d560c5 Merge remote-tracking branch 'origin/main' 2026-01-04 14:35:38 +08:00
c3a9de112d 新曾业务bean和相关数据持久化,controller restful 接口 2026-01-04 14:35:22 +08:00
Lxq
06af1fec4e 完善设备管理+PLC设备管理 2025-12-31 14:48:27 +08:00
Lxq
2978291de1 Merge branch 'main' of http://223.71.122.54:3000/rczn001/RCZN-bs-program 2025-12-30 10:52:15 +08:00
Lxq
1dff9e79f4 功能岛管理+设备管理 2025-12-30 10:51:47 +08:00
65 changed files with 13128 additions and 1170 deletions

12
rc_autoplc_backend/.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK" />
</project>

6
rc_autoplc_backend/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

54
rc_autoplc_backend/.idea/workspace.xml generated Normal file
View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="d47bc58e-d36b-422c-92c6-393c467a25d3" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="MavenImportPreferences">
<option name="generalSettings">
<MavenGeneralSettings>
<option name="useMavenConfig" value="true" />
</MavenGeneralSettings>
</option>
</component>
<component name="ProjectId" id="37uy9ngFoDUVUddX8Yg9afHWRsn" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"WebServerToolWindowFactoryState": "false",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="d47bc58e-d36b-422c-92c6-393c467a25d3" name="Changes" comment="" />
<created>1767767108230</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1767767108230</updated>
<workItem from="1767767109651" duration="29000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
</project>

View File

@@ -95,3 +95,34 @@ openapi:
description: 本地开发环境
- url: http://47.116.126.33:9090 # 测试环境地址
description: 测试环境
# Spring Boot 日志核心配置
logging:
# 1. 全局日志级别(可细化到包/类)
level:
root: INFO # 根日志级别(默认)
com.rczn.rcznautoplc: DEBUG # 自定义包级别(调试业务代码)
org.springframework: INFO # Spring 框架日志级别(减少冗余)
com.zaxxer.hikari: ERROR # 数据库连接池日志级别(仅输出错误)
mybatis: DEBUG # MyBatis SQL 日志级别(输出执行的 SQL
# 2. 日志输出配置(控制台 + 文件)
pattern:
# console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n" # 控制台格式
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n" # 文件格式
# 3. 文件输出配置
file:
name: ./logs/rczn-autoplc.log # 日志文件路径(相对路径/绝对路径)
max-size: 10MB # 单个日志文件大小上限(超过则分割)
max-history: 30 # 日志文件保留天数
total-size-cap: 1GB # 日志文件总大小上限
clean-history-on-start: false # 启动时是否清理历史日志
# 4. 日志分割(按大小/时间Logback 原生支持)
logback:
rollingpolicy:
file-name-pattern: ./logs/rczn-autoplc-%d{yyyy-MM-dd}.%i.log # 分割后的文件名(%i 是序号,处理同天多文件)
max-file-size: 100MB # 单个文件大小(覆盖上面的 max-size
max-history: 30 # 保留天数
total-size-cap: 1GB # 总大小

View File

@@ -69,6 +69,12 @@
<version>7.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-common</artifactId>
<version>2.3.0</version>
<scope>compile</scope>
</dependency>
<!-- 这里可以添加PLC通信相关的依赖 -->
<!-- 例如:

View File

@@ -7,7 +7,9 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -142,14 +144,13 @@ public class DevInfoController {
@Operation(summary = "分页查询设备", description = "支持设备名称、型号、IP、状态等多条件过滤默认查询未删除设备")
@Parameters({
@Parameter(name = "pageNum", description = "页码(必填)", required = true, example = "1", in = ParameterIn.QUERY),
@Parameter(name = "pageSize", description = "每页条数(必填)", required = true, example = "10", in = ParameterIn.QUERY),
@Parameter(name = "devName", description = "设备名称(可选,模糊查询)", required = false, example = "PLC设备", in = ParameterIn.QUERY),
@Parameter(name = "devModel", description = "设备型号(可选,模糊查询)", required = false, example = "S7-1200", in = ParameterIn.QUERY),
@Parameter(name = "ipAddr", description = "IP地址可选精准查询", required = false, example = "192.168.1.100", in = ParameterIn.QUERY),
@Parameter(name = "status", description = "设备状态可选精准查询0-空闲1-运行4-故障)", required = false, example = "0", in = ParameterIn.QUERY),
@Parameter(name = "protocolType", description = "协议类型(可选,精准查询)", required = false, example = "Modbus", in = ParameterIn.QUERY)
@Parameter(name = "pageSize", description = "每页条数(必填)", required = true, example = "10", in = ParameterIn.QUERY)
// @Parameter(name = "devName", description = "设备名称(可选,模糊查询)", required = false, example = "PLC设备", in = ParameterIn.QUERY),
// @Parameter(name = "devModel", description = "设备型号(可选,模糊查询)", required = false, example = "S7-1200", in = ParameterIn.QUERY)
})
public ResponseEntity<Map<String, Object>> getDevInfoPage(
@ParameterObject // 核心注解:标记该实体类从查询参数取值
@Schema(description = "设备查询条件(可选)") // 补充描述
DevInfo devInfo, // 接收查询条件devName、status等
@RequestParam Integer pageNum,
@RequestParam Integer pageSize) {

View File

@@ -14,6 +14,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/devParam")
@Tag(name = "设备参数管理", description = "设备参数增删改查+分页查询接口")
@@ -44,6 +46,24 @@ public class DevParamController {
return Result.success(pageBean);
}
/**
* 根据条件,查询设备参数
*/
@GetMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "查询设备参数", description = "支持设备ID精准查询、参数名称/值模糊查询")
@Parameters({
@Parameter(name = "devId", description = "设备ID可选精准查询", required = false, example = "1", in = ParameterIn.QUERY),
@Parameter(name = "paramName", description = "参数名称(可选,模糊查询)", required = false, example = "温度", in = ParameterIn.QUERY),
@Parameter(name = "paramValue", description = "参数值(可选,模糊查询)", required = false, example = "25", in = ParameterIn.QUERY)
})
public Result getDevParamPage(
@RequestParam(required = false) Integer devId,
@RequestParam(required = false) String paramName,
@RequestParam(required = false) String paramValue) {
List<DevParam> pageBean = devParamService.selectList( devId, paramName, paramValue);
return Result.success(pageBean);
}
/**
* 根据ID查询设备参数
*/

View File

@@ -14,9 +14,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/flowInfo")
@Tag(name = "流程信息管理", description = "流程信息增删改查+分页查询接口")
@Tag(name = "标准流程管理", description = "流程信息增删改查+分页查询接口")
public class FlowInfoController {
@Autowired
@@ -42,6 +44,21 @@ public class FlowInfoController {
return Result.success(pageBean);
}
@GetMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "查询流程信息", description = "支持流程排序精准查询、流程名称/岛屿ID列表模糊查询")
@Parameters({
@Parameter(name = "flowIndex", description = "流程排序(可选,精准查询)", required = false, example = "1", in = ParameterIn.QUERY),
@Parameter(name = "flowName", description = "流程名称(可选,模糊查询)", required = false, example = "检测流程", in = ParameterIn.QUERY),
@Parameter(name = "islandIdList", description = "岛屿ID列表可选模糊查询", required = false, example = "1,2,3", in = ParameterIn.QUERY)
})
public Result getFlowInfoList(
@RequestParam(required = false) Integer flowIndex,
@RequestParam(required = false) String flowName,
@RequestParam(required = false) String islandIdList) {
List<FlowInfo> list = flowInfoService.selectList( flowIndex, flowName, islandIdList);
return Result.success(list);
}
@GetMapping(value = "/getById/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "查询单个流程信息", description = "根据流程信息ID查询详情")
public Result getFlowInfoById(@PathVariable Long id) {

View File

@@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/goodsFlowStatus")
@Tag(name = "样品流程状态管理", description = "样品流程状态增删改查+分页查询接口")
@Tag(name = "样品流程信息管理", description = "样品流程状态增删改查+分页查询接口")
public class GoodsFlowStatusController {
@Autowired

View File

@@ -14,6 +14,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/stepInfo")
@Tag(name = "步骤信息管理", description = "步骤信息增删改查+分页查询接口")
@@ -40,6 +42,20 @@ public class StepInfoController {
return Result.success(pageBean);
}
@GetMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "查询步骤信息", description = "支持岛屿ID精准查询、步骤名称模糊查询")
@Parameters({
@Parameter(name = "islandId", description = "岛屿ID可选精准查询", required = false, example = "1", in = ParameterIn.QUERY),
@Parameter(name = "stepName", description = "步骤名称(可选,模糊查询)", required = false, example = "检测", in = ParameterIn.QUERY)
})
public Result getStepInfoList(
@RequestParam(required = false) Integer islandId,
@RequestParam(required = false) String stepName) {
List<StepInfo> list = stepInfoService.selectList( islandId, stepName);
return Result.success(list);
}
@GetMapping(value = "/getById/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "查询单个步骤信息", description = "根据步骤信息ID查询详情")
public Result getStepInfoById(@PathVariable Long id) {

View File

@@ -5,10 +5,17 @@ import com.rczn.domain.BaseBean;
import java.time.LocalDateTime;
public class DevInfo extends BaseBean {
//功能岛外键
private Integer islandId;
//设备名称
private String devName;
//设备型号
private String devModel;
//设备IP地址
private String ipAddr;
private Integer port;
@@ -19,7 +26,7 @@ public class DevInfo extends BaseBean {
private Integer status; // 0-空闲1-运行4-故障
private String desc;
private String devDesc;
public DevInfo() {
}
@@ -28,6 +35,14 @@ public class DevInfo extends BaseBean {
super(id, createId, createTime, updateId, updateTime, delSign, remark);
}
public Integer getIslandId() {
return islandId;
}
public void setIslandId(Integer islandId) {
this.islandId = islandId;
}
public String getDevName() {
return devName;
}
@@ -84,11 +99,11 @@ public class DevInfo extends BaseBean {
this.status = status;
}
public String getDesc() {
return desc;
public String getDevDesc() {
return devDesc;
}
public void setDesc(String desc) {
this.desc = desc;
public void setDevDesc(String devDesc) {
this.devDesc = devDesc;
}
}

View File

@@ -11,7 +11,12 @@ public class DevParam extends BaseBean {
private String paramValue;
private String desc;
private String paramType;
private String paramUnit;
private String formType;
private String paramDesc;
private DevInfo devInfo;
@@ -46,12 +51,36 @@ public class DevParam extends BaseBean {
this.paramValue = paramValue;
}
public String getDesc() {
return desc;
public String getParamType() {
return paramType;
}
public void setDesc(String desc) {
this.desc = desc;
public void setParamType(String paramType) {
this.paramType = paramType;
}
public String getParamUnit() {
return paramUnit;
}
public void setParamUnit(String paramUnit) {
this.paramUnit = paramUnit;
}
public String getFormType() {
return formType;
}
public void setFormType(String formType) {
this.formType = formType;
}
public String getParamDesc() {
return paramDesc;
}
public void setParamDesc(String paramDesc) {
this.paramDesc = paramDesc;
}
public DevInfo getDevInfo() {

View File

@@ -11,6 +11,20 @@ public class FlowInfo extends BaseBean {
private String islandIdList;
private String busyCode;
private String goodsCode;
private String goodsName;
private String checkCode; // 校验码
private String programName; // 程序名称
private String testMethod; // 测试方法
private String scanNum; //扫描编号
public FlowInfo() {
}
@@ -18,6 +32,62 @@ public class FlowInfo extends BaseBean {
super(id, createId, createTime, updateId, updateTime, delSign, remark);
}
public String getBusyCode() {
return busyCode;
}
public void setBusyCode(String busyCode) {
this.busyCode = busyCode;
}
public String getGoodsCode() {
return goodsCode;
}
public void setGoodsCode(String goodsCode) {
this.goodsCode = goodsCode;
}
public String getGoodsName() {
return goodsName;
}
public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
}
public String getCheckCode() {
return checkCode;
}
public void setCheckCode(String checkCode) {
this.checkCode = checkCode;
}
public String getProgramName() {
return programName;
}
public void setProgramName(String programName) {
this.programName = programName;
}
public String getTestMethod() {
return testMethod;
}
public void setTestMethod(String testMethod) {
this.testMethod = testMethod;
}
public String getScanNum() {
return scanNum;
}
public void setScanNum(String scanNum) {
this.scanNum = scanNum;
}
public Integer getFlowIndex() {
return flowIndex;
}

View File

@@ -7,6 +7,16 @@ import java.time.LocalDateTime;
public class StepInfo extends BaseBean {
private Integer islandId;
//设备id
private Integer devId;
private Integer flowId;
private String paramName;
private String paramType;
private String paramUnit;
private String paramValue;
private String formType;
private String stepName;
private String stepDesc;
@@ -20,6 +30,62 @@ public class StepInfo extends BaseBean {
super(id, createId, createTime, updateId, updateTime, delSign, remark);
}
public Integer getFlowId() {
return flowId;
}
public void setFlowId(Integer flowId) {
this.flowId = flowId;
}
public Integer getDevId() {
return devId;
}
public void setDevId(Integer devId) {
this.devId = devId;
}
public String getParamName() {
return paramName;
}
public void setParamName(String paramName) {
this.paramName = paramName;
}
public String getParamType() {
return paramType;
}
public void setParamType(String paramType) {
this.paramType = paramType;
}
public String getParamUnit() {
return paramUnit;
}
public void setParamUnit(String paramUnit) {
this.paramUnit = paramUnit;
}
public String getParamValue() {
return paramValue;
}
public void setParamValue(String paramValue) {
this.paramValue = paramValue;
}
public String getFormType() {
return formType;
}
public void setFormType(String formType) {
this.formType = formType;
}
public Integer getIslandId() {
return islandId;
}

View File

@@ -4,12 +4,16 @@ import com.github.pagehelper.PageInfo;
import com.rczn.domain.PageBean;
import com.rczn.rcznautoplc.domain.DevParam;
import java.util.List;
public interface DevParamService {
/**
* 分页查询
*/
PageInfo<DevParam> selectPage(Integer pageNum, Integer pageSize, Integer devId, String paramName, String paramValue);
List<DevParam> selectList(Integer devId, String paramName, String paramValue);
/**
* 根据ID查询
*/

View File

@@ -4,8 +4,11 @@ import com.github.pagehelper.PageInfo;
import com.rczn.domain.PageBean;
import com.rczn.rcznautoplc.domain.FlowInfo;
import java.util.List;
public interface FlowInfoService {
PageInfo<FlowInfo> selectPage(Integer pageNum, Integer pageSize, Integer flowIndex, String flowName, String islandIdList);
List<FlowInfo> selectList( Integer flowIndex, String flowName, String islandIdList);
FlowInfo selectById(Long id);
Long insert(FlowInfo flowInfo);
Boolean update(FlowInfo flowInfo);

View File

@@ -3,8 +3,11 @@ package com.rczn.rcznautoplc.service;
import com.github.pagehelper.PageInfo;
import com.rczn.rcznautoplc.domain.StepInfo;
import java.util.List;
public interface StepInfoService {
PageInfo<StepInfo> selectPage(Integer pageNum, Integer pageSize, Integer islandId, String stepName);
List<StepInfo> selectList( Integer islandId, String stepName);
StepInfo selectById(Long id);
Long insert(StepInfo stepInfo);
Boolean update(StepInfo stepInfo);

View File

@@ -32,6 +32,25 @@ public class DevParamServiceImpl implements DevParamService {
return new PageInfo<>(list);
}
/**
* 根据条件查询设备参数列表
* @param devId
* @param paramName
* @param paramValue
* @return
*/
@Override
public List<DevParam> selectList(Integer devId, String paramName, String paramValue) {
// 1. 构建查询条件
DevParam queryParam = new DevParam();
queryParam.setDevId(devId);
queryParam.setParamName(paramName);
queryParam.setParamValue(paramValue);
// 3. 执行查询
List<DevParam> list = devParamMapper.selectPage(queryParam);
return list;
}
@Override
public DevParam selectById(Long id) {
if (id == null) {

View File

@@ -28,6 +28,23 @@ public class FlowInfoServiceImpl implements FlowInfoService {
return new PageInfo<>(list);
}
/**
* 查询列表g根据国标流程
* @param flowIndex
* @param flowName
* @param islandIdList
* @return
*/
@Override
public List<FlowInfo> selectList(Integer flowIndex, String flowName, String islandIdList) {
FlowInfo queryParam = new FlowInfo();
queryParam.setFlowIndex(flowIndex);
queryParam.setFlowName(flowName);
queryParam.setIslandIdList(islandIdList);
List<FlowInfo> list = flowInfoMapper.selectPage(queryParam);
return list;
}
@Override
public FlowInfo selectById(Long id) {
if (id == null) throw new IllegalArgumentException("ID不能为空");

View File

@@ -26,6 +26,15 @@ public class StepInfoServiceImpl implements StepInfoService {
return new PageInfo<>(list);
}
@Override
public List<StepInfo> selectList(Integer islandId, String stepName) {
StepInfo queryParam = new StepInfo();
queryParam.setIslandId(islandId);
queryParam.setStepName(stepName);
List<StepInfo> list = stepInfoMapper.selectPage(queryParam);
return list;
}
@Override
public StepInfo selectById(Long id) {
if (id == null) throw new IllegalArgumentException("ID不能为空");

View File

@@ -8,7 +8,7 @@
<!-- 设备专属字段 -->
<sql id="devColumn">
dev_name, dev_model, ip_addr, port, protocol_type, company, status, `desc`
island_id, dev_name, dev_model, ip_addr, port, protocol_type, company, status, dev_desc
</sql>
<!-- 结果集映射(下划线转驼峰已开启,可简化) -->
@@ -22,6 +22,7 @@
<result column="del_sign" property="delSign"/>
<result column="remark" property="remark"/>
<!-- 设备专属字段 -->
<result column="island_id" property="islandId"/>
<result column="dev_name" property="devName"/>
<result column="dev_model" property="devModel"/>
<result column="ip_addr" property="ipAddr"/>
@@ -29,14 +30,15 @@
<result column="protocol_type" property="protocolType"/>
<result column="company" property="company"/>
<result column="status" property="status"/>
<result column="`desc`" property="desc"/> <!-- desc是MySQL关键字需用反引号包裹 -->
<result column="dev_desc" property="devDesc"/>
</resultMap>
<!-- 新增设备(空字段忽略) -->
<!-- 新增设备(空字段忽略,规范优化版 -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO tb_dev_info (
<trim prefix="" suffix="" suffixOverrides=",">
<!-- 专属字段 -->
<!-- 设备专属字段(修正 test 表达式 + 关键字转义) -->
<if test="islandId != null">island_id,</if>
<if test="devName != null and devName != ''">dev_name,</if>
<if test="devModel != null and devModel != ''">dev_model,</if>
<if test="ipAddr != null and ipAddr != ''">ip_addr,</if>
@@ -44,17 +46,19 @@
<if test="protocolType != null and protocolType != ''">protocol_type,</if>
<if test="company != null and company != ''">company,</if>
<if test="status != null">status,</if>
<if test="desc != null and desc != ''">`desc`,</if>
<!-- 通用字段 -->
<if test="devDesc != null and devDesc != ''">`dev_desc`,</if> <!-- 核心修正dev_desc → devDesc + 反引号 -->
<!-- 通用字段(仅保留必要的人工传入字段,时间字段由数据库/代码自动生成) -->
<if test="createId != null">create_id,</if>
<if test="createTime != null">create_time,</if>
<if test="updateId != null">update_id,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null and remark != ''">remark,</if>
<!-- 固定写入:创建/更新时间由数据库生成删除标识默认0 -->
create_time,
update_time,
del_sign
</trim>
) VALUES (
<trim prefix="" suffix="" suffixOverrides=",">
<!-- 专属字段 -->
<!-- 设备专属字段(与上方字段一一对应) -->
<if test="islandId != null">#{islandId},</if>
<if test="devName != null and devName != ''">#{devName},</if>
<if test="devModel != null and devModel != ''">#{devModel},</if>
<if test="ipAddr != null and ipAddr != ''">#{ipAddr},</if>
@@ -62,17 +66,19 @@
<if test="protocolType != null and protocolType != ''">#{protocolType},</if>
<if test="company != null and company != ''">#{company},</if>
<if test="status != null">#{status},</if>
<if test="desc != null and desc != ''">#{desc},</if>
<!-- 通用字段 -->
<if test="devDesc != null and devDesc != ''">#{devDesc},</if> <!-- 核心修正dev_desc → devDesc -->
<!-- 通用字段(与上方字段一一对应) -->
<if test="createId != null">#{createId},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateId != null">#{updateId},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null and remark != ''">#{remark},</if>
<!-- 固定值:创建/更新时间用数据库当前时间删除标识默认0 -->
NOW(),
NOW(),
0
</trim>
)
</insert>
<!-- 逻辑删除设备 -->
<update id="deleteById">
UPDATE tb_dev_info
@@ -80,10 +86,12 @@
WHERE id = #{id} AND del_sign = 0
</update>
<!-- 根据ID更新设备非空字段才更新 -->
<!-- 根据ID更新设备非空字段才更新,规范优化版 -->
<update id="updateById">
UPDATE tb_dev_info
<set>
<!-- 设备专属字段修正test表达式 + 关键字转义 + 规范空格 -->
<if test="islandId != null">island_id = #{islandId},</if>
<if test="devName != null and devName != ''">dev_name = #{devName},</if>
<if test="devModel != null and devModel != ''">dev_model = #{devModel},</if>
<if test="ipAddr != null and ipAddr != ''">ip_addr = #{ipAddr},</if>
@@ -91,14 +99,33 @@
<if test="protocolType != null and protocolType != ''">protocol_type = #{protocolType},</if>
<if test="company != null and company != ''">company = #{company},</if>
<if test="status != null">status = #{status},</if>
<if test="desc != null and desc != ''">`desc` = #{desc},</if>
<!-- 核心修正dev_desc → devDesc + 反引号转义 -->
<if test="devDesc != null and devDesc != ''">`dev_desc` = #{devDesc},</if>
<!-- 通用字段 -->
<if test="updateId != null">update_id = #{updateId},</if>
<if test="remark != null and remark != ''">remark = #{remark},</if>
<!-- 固定更新时间:无论是否有其他字段更新,都刷新更新时间 -->
update_time = NOW()
</set>
<!-- 条件ID精准匹配 + 未删除,防止更新已删除数据 -->
WHERE id = #{id} AND del_sign = 0
<!-- 兜底:确保至少有一个业务字段更新(避免仅更新时间) -->
<if test="!(islandId != null
or (devName != null and devName != '')
or (devModel != null and devModel != '')
or (ipAddr != null and ipAddr != '')
or port != null
or (protocolType != null and protocolType != '')
or (company != null and company != '')
or status != null
or (devDesc != null and devDesc != '')
or updateId != null
or (remark != null and remark != ''))">
AND 1 = 0 <!-- 无更新字段时让SQL条件不成立避免无效更新 -->
</if>
</update>
<!-- 根据ID查询设备 -->
<select id="selectById" resultMap="DevResultMap">
SELECT
@@ -119,6 +146,10 @@
<if test="devName != null and devName != ''">
AND dev_name LIKE CONCAT('%', #{devName}, '%')
</if>
<!-- 设备所属岛屿精准查询 -->
<if test="islandId != null">
AND island_id = #{islandId}
</if>
<!-- 设备型号模糊查询 -->
<if test="devModel != null and devModel != ''">
AND dev_model LIKE CONCAT('%', #{devModel}, '%')
@@ -146,6 +177,9 @@
<if test="devName != null and devName != ''">
AND dev_name LIKE CONCAT('%', #{devName}, '%')
</if>
<if test="islandId != null">
AND island_id = #{islandId}
</if>
<if test="devModel != null and devModel != ''">
AND dev_model LIKE CONCAT('%', #{devModel}, '%')
</if>

View File

@@ -8,17 +8,37 @@
<!-- DevParam 业务字段 -->
<sql id="devParamColumn">
dev_id, param_name, param_value, `desc`
dev_id, param_name, param_value,param_type,param_unit,form_type, param_desc
</sql>
<!-- 新增 -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO tb_dev_param (
<include refid="devParamColumn"/>,
create_id, create_time, update_id, update_time, del_sign
<trim suffixOverrides=",">
<if test="devId != null">dev_id, </if>
<if test="paramName != null and paramName != ''">param_name, </if>
<if test="paramValue != null and paramValue != ''">param_value, </if>
<if test="paramType != null and paramType != ''">param_type, </if>
<if test="paramUnit != null and paramUnit != ''">param_unit, </if>
<if test="formType != null and formType != ''">form_type, </if>
<if test="paramDesc != null and paramDesc != ''">param_desc, </if>
<if test="createId != null">create_id, </if>
<if test="updateId != null">update_id, </if>
create_time, update_time, del_sign
</trim>
) VALUES (
#{devId}, #{paramName}, #{paramValue}, #{desc},
#{createId}, NOW(), #{updateId}, NOW(), 0
<trim suffixOverrides=",">
<if test="devId != null">#{devId}, </if>
<if test="paramName != null and paramName != ''">#{paramName}, </if>
<if test="paramValue != null and paramValue != ''">#{paramValue}, </if>
<if test="paramType != null and paramType != ''">#{paramType}, </if>
<if test="paramUnit != null and paramUnit != ''">#{paramUnit}, </if>
<if test="formType != null and formType != ''">#{formType}, </if>
<if test="paramDesc != null and paramDesc != ''">#{paramDesc}, </if>
<if test="createId != null">#{createId}, </if>
<if test="updateId != null">#{updateId}, </if>
NOW(), NOW(), 0
</trim>
)
</insert>
@@ -29,7 +49,10 @@
<if test="devId != null">dev_id = #{devId},</if>
<if test="paramName != null and paramName != ''">param_name = #{paramName},</if>
<if test="paramValue != null and paramValue != ''">param_value = #{paramValue},</if>
<if test="desc != null and desc != ''">`desc` = #{desc},</if>
<if test="paramType != null and paramType != ''">param_type = #{paramType},</if>
<if test="paramUnit != null and paramUnit != ''">param_unit = #{paramUnit},</if>
<if test="formType != null and formType != ''">form_type = #{formType},</if>
<if test="paramDesc != null and paramDesc != ''">param_desc = #{paramDesc},</if>
<if test="updateId != null">update_id = #{updateId},</if>
update_time = NOW()
</set>

View File

@@ -6,16 +6,53 @@
</sql>
<sql id="flowInfoColumn">
flow_index, flow_name, island_id_list
flow_index, flow_name, island_id_list,busy_code,goods_code,goods_name,check_code,program_name,test_method,scan_num
</sql>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO tb_flow_info (
<include refid="flowInfoColumn"/>,
create_id, create_time, update_id, update_time, del_sign
<trim suffixOverrides=",">
<!-- 以下是基础表字段 -->
<if test="createId != null" >create_id, </if>
<if test="updateId != null" >update_id, </if>
<if test="createTime != null" >create_time, </if>
<if test="updateTime != null" >update_time, </if>
<if test="remark != null" >remark, </if>
<!-- 以下是表本身字段 -->
<if test="flowIndex != null" >flow_index, </if>
<if test="flowName != null and flowName != ''" >flow_name, </if>
<if test="islandIdList != null and islandIdList != ''" >island_id_list, </if>
<if test="busyCode != null and busyCode != ''" >busy_code, </if>
<if test="goodsCode != null and goodsCode != ''" >goods_code, </if>
<if test="goodsName != null and goodsName != ''" >goods_name, </if>
<if test="checkCode != null and checkCode != ''" >check_code, </if>
<if test="programName != null and programName != ''" >program_name, </if>
<if test="testMethod != null and testMethod != ''" >test_method, </if>
<if test="scanNum != null and scanNum != ''" >scan_num, </if>
</trim>
) VALUES (
#{flowIndex}, #{flowName}, #{islandIdList},
#{createId}, NOW(), #{updateId}, NOW(), 0
<trim suffixOverrides=",">
<if test="createId != null" >#{createId}, </if>
<if test="updateId != null" >#{updateId}, </if>
<if test="createTime != null" >#{createTime}, </if>
<if test="updateTime != null" >#{updateTime}, </if>
<if test="remark != null" >#{remark}, </if>
<!-- 以下是表本身字段 -->
<if test="flowIndex != null" >#{flowIndex}, </if>
<if test="flowName != null and flowName != ''" >#{flowName}, </if>
<if test="islandIdList != null and islandIdList != ''" >#{islandIdList}, </if>
<if test="busyCode != null and busyCode != ''" >#{busyCode}, </if>
<if test="goodsCode != null and goodsCode != ''" >#{goodsCode}, </if>
<if test="goodsName != null and goodsName != ''" >#{goodsName}, </if>
<if test="checkCode != null and checkCode != ''" >#{checkCode}, </if>
<if test="programName != null and programName != ''" >#{programName}, </if>
<if test="testMethod != null and testMethod != ''" >#{testMethod}, </if>
<if test="scanNum != null and scanNum != ''" >#{scanNum}, </if>
</trim>
)
</insert>
@@ -25,7 +62,16 @@
<if test="flowIndex != null">flow_index = #{flowIndex},</if>
<if test="flowName != null and flowName != ''">flow_name = #{flowName},</if>
<if test="islandIdList != null and islandIdList != ''">island_id_list = #{islandIdList},</if>
<if test="busyCode!=null and busyCode != ''">busy_code = #{busyCode},</if>
<if test="goodsCode!=null and goodsCode != ''">goods_code = #{goodsCode},</if>
<if test="goodsName!=null and goodsName != ''">goods_name = #{goodsName},</if>
<if test="checkCode!=null and checkCode != ''">check_code = #{checkCode},</if>
<if test="programName!=null and programName != ''">program_name = #{programName},</if>
<if test="testMethod!=null and testMethod != ''">test_method = #{testMethod},</if>
<if test="scanNum!=null and scanNum != ''">scan_num = #{scanNum},</if>
<if test="updateId != null">update_id = #{updateId},</if>
update_time = NOW()
</set>
WHERE id = #{id} AND del_sign = 0
@@ -54,6 +100,13 @@
<if test="flowIndex != null">AND flow_index = #{flowIndex}</if>
<if test="flowName != null and flowName != ''">AND flow_name LIKE CONCAT('%', #{flowName}, '%')</if>
<if test="islandIdList != null and islandIdList != ''">AND island_id_list LIKE CONCAT('%', #{islandIdList}, '%')</if>
<if test="busyCode != null and busyCode != ''">AND busy_code LIKE CONCAT('%', #{busyCode}, '%')</if>
<if test="goodsCode != null and goodsCode != ''">AND goods_code LIKE CONCAT('%', #{goodsCode}, '%')</if>
<if test="goodsName != null and goodsName != ''">AND goods_name LIKE CONCAT('%', #{goodsName}, '%')</if>
<if test="checkCode != null and checkCode != ''">AND check_code LIKE CONCAT('%', #{checkCode}, '%')</if>
<if test="programName != null and programName != ''">AND program_name LIKE CONCAT('%', #{programName}, '%')</if>
<if test="testMethod != null and testMethod != ''">AND test_method LIKE CONCAT('%', #{testMethod}, '%')</if>
<if test="scanNum != null and scanNum != ''">AND scan_num LIKE CONCAT('%', #{scanNum}, '%')</if>
ORDER BY flow_index ASC
</select>
</mapper>

View File

@@ -6,16 +6,54 @@
</sql>
<sql id="stepInfoColumn">
island_id, step_name, step_desc
island_id,dev_id, step_name, step_desc,flow_id,param_name,param_type,param_unit,param_value,form_type
</sql>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO tb_step_info (
<include refid="stepInfoColumn"/>,
create_id, create_time, update_id, update_time, del_sign
<trim suffixOverrides=",">
<!-- 基础表字段 -->
<if test="createId != null">create_id, </if>
<if test="createTime != null">create_time, </if>
<if test="updateId != null">update_id, </if>
<if test="updateTime != null">update_time, </if>
<if test="remark != null ">remark, </if>
<!-- 步骤表字段 -->
<if test="islandId != null">island_id, </if>
<if test="devId != null">dev_id, </if>
<if test="stepName != null and stepName != ''">step_name, </if>
<if test="stepDesc != null and stepDesc != ''">step_desc, </if>
<if test="flowId != null">flow_id, </if>
<if test="paramName != null and paramName != ''">param_name, </if>
<if test="paramType != null and paramType != ''">param_type, </if>
<if test="paramUnit != null and paramUnit != ''">param_unit, </if>
<if test="paramValue != null and paramValue != ''">param_value, </if>
<if test="formType != null and formType != ''">form_type, </if>
</trim>
) VALUES (
#{islandId}, #{stepName}, #{stepDesc},
#{createId}, NOW(), #{updateId}, NOW(), 0
<trim suffixOverrides=",">
<!-- 基础表字段 -->
<if test="createId != null">#{createId}, </if>
<if test="createTime != null">#{createTime}, </if>
<if test="updateId != null">#{updateId}, </if>
<if test="updateTime != null">#{updateTime}, </if>
<if test="remark != null ">#{remark}, </if>
<!-- 步骤表字段 -->
<if test="islandId != null">#{islandId}, </if>
<if test="devId != null">#{devId}, </if>
<if test="stepName != null and stepName != ''">#{stepName}, </if>
<if test="stepDesc != null and stepDesc != ''">#{stepDesc}, </if>
<if test="flowId != null">#{flowId}, </if>
<if test="paramName != null and paramName != ''">#{paramName}, </if>
<if test="paramType != null and paramType != ''">#{paramType}, </if>
<if test="paramUnit != null and paramUnit != ''">#{paramUnit}, </if>
<if test="paramValue != null and paramValue != ''">#{paramValue}, </if>
<if test="formType != null and formType != ''">#{formType}, </if>
</trim>
)
</insert>
@@ -23,9 +61,17 @@
UPDATE tb_step_info
<set>
<if test="islandId != null">island_id = #{islandId},</if>
<if test="devId != null">dev_id = #{devId},</if>
<if test="stepName != null and stepName != ''">step_name = #{stepName},</if>
<if test="stepDesc != null and stepDesc != ''">step_desc = #{stepDesc},</if>
<if test="updateId != null">update_id = #{updateId},</if>
<if test="remark != null ">remark = #{remark},</if>
<if test="flowId != null">flow_id = #{flowId},</if>
<if test="paramName != null and paramName != ''">param_name = #{paramName},</if>
<if test="paramType != null and paramType != ''">param_type = #{paramType},</if>
<if test="paramUnit != null and paramUnit != ''">param_unit = #{paramUnit},</if>
<if test="paramValue != null and paramValue != ''">param_value = #{paramValue},</if>
<if test="formType != null and formType != ''">form_type = #{formType},</if>
update_time = NOW()
</set>
WHERE id = #{id} AND del_sign = 0
@@ -52,7 +98,13 @@
FROM tb_step_info
WHERE del_sign = 0
<if test="islandId != null">AND island_id = #{islandId}</if>
<if test="devId != null">AND dev_id = #{devId}</if>
<if test="stepName != null and stepName != ''">AND step_name LIKE CONCAT('%', #{stepName}, '%')</if>
<if test="flowId != null">AND flow_id = #{flowId}</if>
<if test="paramName != null and paramName != ''">AND param_name LIKE CONCAT('%', #{paramName}, '%')</if>
<if test="paramType != null and paramType != ''">AND param_type LIKE CONCAT('%', #{paramType}, '%')</if>
<if test="paramUnit != null and paramUnit != ''">AND param_unit LIKE CONCAT('%', #{paramUnit}, '%')</if>
<if test="paramValue != null and paramValue != ''">AND param_value LIKE CONCAT('%', #{paramValue}, '%')</if>
ORDER BY create_time DESC
</select>
</mapper>

View File

@@ -4,6 +4,7 @@ import com.rczn.utils.JwtUtil;
import com.rczn.utils.ThreadLocalUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Map;

View File

@@ -2,9 +2,9 @@
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<link rel="icon" href="/public/Laboratory.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
<title>北京融创智能仪器管理系统</title>
</head>
<body>
<div id="app"></div>

File diff suppressed because it is too large Load Diff

View File

@@ -14,11 +14,15 @@
"type-check": "vue-tsc --build"
},
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"axios": "^1.13.2",
"echarts": "^6.0.0",
"element-plus": "^2.12.0",
"html2canvas": "^1.4.1",
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"vue": "^3.5.25",
"vue-echarts": "^8.0.1",
"vue-router": "^4.6.3"
},
"devDependencies": {
@@ -27,6 +31,7 @@
"@vitejs/plugin-vue": "^6.0.2",
"@vue/tsconfig": "^0.8.1",
"npm-run-all2": "^8.0.4",
"sass-embedded": "^1.97.3",
"typescript": "~5.9.0",
"vite": "^7.2.4",
"vite-plugin-vue-devtools": "^8.0.5",

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,5 +1,51 @@
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
import { onMounted, onBeforeUnmount } from 'vue'
import { useAuthStore } from '@/stores/auth'
// 清除token和用户信息
const clearAuthInfo = () => {
const authStore = useAuthStore()
// 清除token这会自动同步到持久化存储
authStore.removeToken()
// 清除localStorage中的用户信息
localStorage.removeItem('username')
// 清除Pinia持久化存储的auth数据pinia-plugin-persistedstate默认使用'pinia-{storeName}'格式)
localStorage.removeItem('pinia-auth')
}
// 页面关闭时清除信息
onMounted(() => {
// 监听页面关闭/刷新事件beforeunload在页面关闭或刷新前触发
const handleBeforeUnload = () => {
clearAuthInfo()
}
// 监听页面卸载事件unload在页面卸载时触发
const handleUnload = () => {
clearAuthInfo()
}
// 监听页面隐藏事件pagehide更可靠可以区分刷新和关闭
const handlePageHide = (event: PageTransitionEvent) => {
// 如果页面被缓存bfcache不清除信息
// 只有在真正关闭时才清除
if (!event.persisted) {
clearAuthInfo()
}
}
window.addEventListener('beforeunload', handleBeforeUnload)
window.addEventListener('unload', handleUnload)
window.addEventListener('pagehide', handlePageHide)
// 清理事件监听器
onBeforeUnmount(() => {
window.removeEventListener('beforeunload', handleBeforeUnload)
window.removeEventListener('unload', handleUnload)
window.removeEventListener('pagehide', handlePageHide)
})
})
</script>
<template>

View File

@@ -0,0 +1,53 @@
import request from '@/utils/request'
export function dicdataadd(data: any) {
return request({
url: '/sysDicData/add',
method: 'post',
data,
})
}
export function dicdatadel(id: string | number) {
return request({
url: `/sysDicData/del/${id}`,
method: 'delete',
})
}
export function dicdataupd(data: any) {
return request({
url: '/sysDicData/update',
method: 'put',
data,
})
}
export function dicdatalist(data: any) {
return request({
url: '/sysDicData/listPage',
method: 'get',
params: data,
})
}
export function dicdatabyid(id: string | number) {
return request({
url: `/sysDicData/getById/${id}`,
method: 'get',
})
}
export function dicdataall() {
return request({
url: `/sysDicData/list`,
method: 'get',
})
}
export function dicdatabydicid(dicId : string | number) {
return request({
url: `/sysDicData/listByDicId/${dicId}`,
method: 'get',
})
}

View File

@@ -0,0 +1,46 @@
import request from '@/utils/request'
export function dictypeadd(data: any) {
return request({
url: '/sysDicType/add',
method: 'post',
data,
})
}
export function dictypedel(id: string | number) {
return request({
url: `/sysDicType/del/${id}`,
method: 'delete',
})
}
export function dictypeupd(data: any) {
return request({
url: '/sysDicType/update',
method: 'put',
data,
})
}
export function dictypelist(data: any) {
return request({
url: '/sysDicType/listPage',
method: 'get',
params: data,
})
}
export function dictypebyid(id: string | number) {
return request({
url: `/sysDicType/getById/${id}`,
method: 'get',
})
}
export function dictypeall() {
return request({
url: `/sysDicType/list`,
method: 'get',
})
}

View File

@@ -37,7 +37,7 @@ export function userRoleById(id: string | number) {
// 根据角色ID查询关联用户先封装不调用
export function userRoleByRoleId(roleId: string | number) {
return request({
url: `/user-role/getByRoleId/${roleId}`,
url: `/user-role/role/${roleId}`,
method: 'get',
})
}
@@ -45,7 +45,7 @@ export function userRoleByRoleId(roleId: string | number) {
// 根据角色ID删除所有关联先封装不调用
export function userRoleDelByRoleId(roleId: string | number) {
return request({
url: `/user-role/delByRoleId/${roleId}`,
url: `/user-role/role/${roleId}`,
method: 'delete',
})
}
@@ -53,7 +53,7 @@ export function userRoleDelByRoleId(roleId: string | number) {
// 根据用户ID查询关联角色先封装不调用
export function userRoleByUserId(userId: string | number) {
return request({
url: `/user-role/getByUserId/${userId}`,
url: `/user-role/user/${userId}`,
method: 'get',
})
}
@@ -61,7 +61,7 @@ export function userRoleByUserId(userId: string | number) {
// 根据用户ID删除所有关联先封装不调用
export function userRoleDelByUserId(userId: string | number) {
return request({
url: `/user-role/delByUserId/${userId}`,
url: `/user-role/user/${userId}`,
method: 'delete',
})
}

View File

@@ -45,3 +45,25 @@ export function userbyid(id: string | number) {
method: 'get',
})
}
export function userlogin(username: string, password: string) {
return request({
url: '/user/login',
method: 'post',
params: {
username,
password,
},
})
}
export function userregister(username: string, password: string) {
return request({
url: '/user/register',
method: 'post',
params: {
username,
password,
},
})
}

View File

@@ -0,0 +1,47 @@
import request from '@/utils/request'
export function devInfoadd(data: any) {
return request({
url: '/devInfo/add',
method: 'post',
data,
})
}
export function devInfodel(id: string | number) {
return request({
url: `/devInfo/del/${id}`,
method: 'delete',
})
}
export function devInfoupd(data: any) {
return request({
url: '/devInfo/update',
method: 'put',
data,
})
}
export function devInfolist(data: any) {
return request({
url: '/devInfo/listPage',
method: 'get',
params: data,
})
}
export function devInfobyid(id: string | number) {
return request({
url: `/devInfo/getById/${id}`,
method: 'get',
})
}
export function devselect(data: any) {
return request({
url: '/devInfo/all',
method: 'get',
params: data,
})
}

View File

@@ -0,0 +1,71 @@
import request from '@/utils/request'
export function devparamadd(data: any) {
return request({
url: '/devParam/add',
method: 'post',
data,
})
}
export function devparamdel(id: string | number) {
return request({
url: `/devParam/del/${id}`,
method: 'delete',
})
}
export function devparamupd(data: any) {
return request({
url: '/devParam/update',
method: 'put',
data,
})
}
export function devparamlist(data: any) {
return request({
url: '/devParam/listPage',
method: 'get',
params: data,
})
}
export function devparambyid(id: string | number) {
return request({
url: `/devParam/getById/${id}`,
method: 'get',
})
}
export function devparamselect(data: any) {
return request({
url: '/devParam/list',
method: 'get',
params: data,
})
}
// 为参数增加设备的devId
export function devparamaddDevId(devId: number, paramId: number) {
return request({
url: '/devParam/addDevId',
method: 'post',
params: {
devId,
paramId,
},
})
}
// 为参数减少设备的devId
export function devparamdelDevId(devId: number, paramId: number) {
return request({
url: '/devParam/delDevId',
method: 'post',
params: {
devId,
paramId,
},
})
}

View File

@@ -0,0 +1,39 @@
import request from '@/utils/request'
export function flowInfoadd(data: any) {
return request({
url: '/flowInfo/add',
method: 'post',
data,
})
}
export function flowInfodel(id: string | number) {
return request({
url: `/flowInfo/del/${id}`,
method: 'delete',
})
}
export function flowInfoupd(data: any) {
return request({
url: '/flowInfo/update',
method: 'put',
data,
})
}
export function flowInfolist(data: any) {
return request({
url: '/flowInfo/listPage',
method: 'get',
params: data,
})
}
export function flowInfobyid(id: string | number) {
return request({
url: `/flowInfo/getById/${id}`,
method: 'get',
})
}

View File

@@ -0,0 +1,47 @@
import request from '@/utils/request'
export function goodsFlowStatusadd(data: any) {
return request({
url: '/goodsFlowStatus/add',
method: 'post',
data,
})
}
export function goodsFlowStatusdel(id: string | number) {
return request({
url: `/goodsFlowStatus/del/${id}`,
method: 'delete',
})
}
export function goodsFlowStatusupd(data: any) {
return request({
url: '/goodsFlowStatus/update',
method: 'put',
data,
})
}
export function goodsFlowStatuslist(data: any) {
return request({
url: '/goodsFlowStatus/listPage',
method: 'get',
params: data,
})
}
export function goodsFlowStatusselect(data: any) {
return request({
url: '/goodsFlowStatus/list',
method: 'get',
params: data,
})
}
export function goodsFlowStatusbyid(id: string | number) {
return request({
url: `/goodsFlowStatus/getById/${id}`,
method: 'get',
})
}

View File

@@ -0,0 +1,39 @@
import request from '@/utils/request'
export function goodsInfoadd(data: any) {
return request({
url: '/goodsInfo/add',
method: 'post',
data,
})
}
export function goodsInfodel(id: string | number) {
return request({
url: `/goodsInfo/del/${id}`,
method: 'delete',
})
}
export function goodsInfoupd(data: any) {
return request({
url: '/goodsInfo/update',
method: 'put',
data,
})
}
export function goodsInfolist(data: any) {
return request({
url: '/goodsInfo/listPage',
method: 'get',
params: data,
})
}
export function goodsInfobyid(id: string | number) {
return request({
url: `/goodsInfo/getById/${id}`,
method: 'get',
})
}

View File

@@ -0,0 +1,108 @@
import request from '@/utils/request'
// 连接PLC设备
export function plcConnect(data: any) {
return request({
url: '/plc/connect',
method: 'post',
params: data
})
}
// 调用PLC设备执行动作
export function plcDoAction(data: any) {
return request({
url: '/plc/doAction',
method: 'post',
params: data
})
}
// 获取全部PLC连接对象
export function plcGetAll() {
return request({
url: '/plc/getAll',
method: 'get'
})
}
// PLC设备获取模式值
export function plcGetModel(ipAddr: string) {
return request({
url: '/plc/getModel',
method: 'get',
params: { ipAddr }
})
}
// 根据IP地址查询PLC连接对象
export function plcGetPlcByIp(ipAddr: string) {
return request({
url: '/plc/getPlcByIp',
method: 'get',
params: { ipAddr }
})
}
// 暂停运行PLC
export function plcPause(ipAddr: string) {
return request({
url: '/plc/pause',
method: 'post',
params: { ipAddr }
})
}
// PLC设备获取状态值
export function plcPlcStatus(ipAddr: string) {
return request({
url: '/plc/plcStatus',
method: 'get',
params: { ipAddr }
})
}
// PLC设备复位故障
export function plcResetError(ipAddr: string) {
return request({
url: '/plc/resetError',
method: 'post',
params: { ipAddr }
})
}
// 运行PLC
export function plcRun(ipAddr: string) {
return request({
url: '/plc/run',
method: 'post',
params: { ipAddr }
})
}
// 为样品执行国标
export function plcRunFlow(data: any) {
return request({
url: '/plc/runFlow',
method: 'post',
params: data
})
}
// PLC设备设置模式
export function plcSetModel(data: any) {
return request({
url: '/plc/setModel',
method: 'post',
params: data
})
}
// 停止PLC
export function plcStop(ipAddr: string) {
return request({
url: '/plc/stop',
method: 'post',
params: { ipAddr }
})
}

View File

@@ -0,0 +1,63 @@
import request from '@/utils/request'
export function stepInfoadd(data: any) {
return request({
url: '/stepInfo/add',
method: 'post',
data,
})
}
export function stepInfoaddlist(data: any) {
return request({
url: '/stepInfo/batchAdd',
method: 'post',
data,
})
}
export function stepInfodel(id: string | number) {
return request({
url: `/stepInfo/del/${id}`,
method: 'delete',
})
}
export function stepInfodellist(data: any[]) {
return request({
url: '/stepInfo/batchDel',
method: 'delete',
data
})
}
export function stepInfoupd(data: any) {
return request({
url: '/stepInfo/update',
method: 'put',
data,
})
}
export function stepInfoupdlist(data: any) {
return request({
url: '/stepInfo/batchUpdate',
method: 'post',
data,
})
}
export function stepInfolist(data: any) {
return request({
url: '/stepInfo/listPage',
method: 'get',
params: data,
})
}
export function stepInfobyid(id: string | number) {
return request({
url: `/stepInfo/getById/${id}`,
method: 'get',
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -1,12 +1,30 @@
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { ElMessage } from 'element-plus'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/login',
name: 'login',
component: () => import('../views/Login.vue'),
meta: { requiresAuth: false },
},
{
path: '/',
component: () => import('../views/Layout.vue'),
meta: { requiresAuth: true },
children: [
{
path: '',
redirect: '/home',
},
{
path: '/home',
name: 'home',
component: () => import('../views/Home.vue'),
},
{
path: '/user',
name: 'user',
@@ -37,20 +55,84 @@ const router = createRouter({
name: 'user-role',
component: () => import('../views/user-role/index.vue'),
},
{
path: '/island-info',
name: 'island-info',
component: () => import('../views/islandInfo/index.vue'),
},
{
path: '/devparam',
name: 'devparam',
component: () => import('../views/devparam/index.vue'),
},
{
path: '/devinfo',
name: 'devinfo',
component: () => import('../views/devinfo/index.vue'),
},
{
path: '/plc-devinfo',
name: 'plc-devinfo',
component: () => import('../views/devinfo/plc.vue'),
},
{
path: '/goods-info',
name: 'goods-info',
component: () => import('../views/goods/index.vue'),
},
{
path: '/flow-info',
name: 'flow-info',
component: () => import('../views/flowinfo/index.vue'),
},
{
path: '/step-info',
name: 'step-info',
component: () => import('../views/stepinfo/index.vue'),
},
{
path: '/plc-device-control',
name: 'plc-device-control',
component: () => import('../views/plcdevicecontrol/index.vue'),
},
],
},
],
})
// 添加全局路由守卫
// router.beforeEach((to, from, next) => {
// const token = localStorage.getItem('token')
router.beforeEach((to, from, next) => {
const authStore = useAuthStore()
const token = authStore.getToken()
// if (to.path !== '/login' && !token) {
// next('/login')
// } else {
// next()
// }
// })
// 如果访问登录页
if (to.path === '/login') {
// 已登录状态下访问登录页,自动跳转到首页
if (token) {
next('/home')
ElMessage.success('已登录,自动跳转')
} else {
// 未登录,正常进入登录页
next()
}
return
}
// 访问需要认证的页面但未登录无token
if (to.meta.requiresAuth && !token) {
ElMessage.warning('请先登录')
next('/login')
return
}
// 如果访问根路径,重定向到首页
if (to.path === '/') {
next('/home')
return
}
// 已登录且访问合法页面,正常放行
next()
})
export default router

View File

@@ -1,38 +0,0 @@
// import router from './index' // 导入你的路由实例与index.ts对应
// import { ElMessage } from 'element-plus'
// import { useAuthStore } from '@/stores/auth' // 你的Pinia auth store路径
// // 路由前置守卫:每次路由跳转前执行
// router.beforeEach((to, from, next) => {
// const authStore = useAuthStore() // 获取auth store实例
// const token = authStore.getToken() // 调用你的getToken()方法获取token
// // 1. 如果访问登录页
// if (to.path === '/login') {
// // 已登录状态下访问登录页,自动跳转到首页/dashboard
// if (token) {
// next('/dashboard')
// ElMessage.success('已登录,自动跳转')
// } else {
// // 未登录,正常进入登录页
// next()
// }
// return
// }
// // 2. 访问非登录页但未登录无token
// if (!token) {
// ElMessage.warning('请先登录')
// next('/login') // 强制跳转到登录页
// return
// }
// // 3. 访问根路径自动跳转到dashboard
// if (to.path === '/') {
// next('/dashboard')
// return
// }
// // 4. 已登录且访问合法页面,正常放行
// next()
// })

View File

@@ -30,6 +30,38 @@ const TokenManager = {
// 删除默认请求头
delete request.defaults.headers.common['Authorization']
},
// 初始化token应用启动时调用
initToken() {
const token = this.getToken()
if (token) {
request.defaults.headers.common['Authorization'] = `Bearer ${token}`
}
},
}
// 应用启动时初始化token
TokenManager.initToken()
// 登录过期提示防重复标志
let isLoginExpiredShown = false
// 处理登录过期(防重复提示)
const handleLoginExpired = () => {
if (!isLoginExpiredShown) {
isLoginExpiredShown = true
// 清除token
TokenManager.removeToken()
// 显示提示消息
ElMessage.error('登录已过期,请重新登录')
// 跳转到登录页
router.replace('/login').finally(() => {
// 延迟重置标志位,确保跳转完成后再允许下次提示
setTimeout(() => {
isLoginExpiredShown = false
}, 1000)
})
}
}
// 请求拦截器
@@ -61,28 +93,65 @@ request.interceptors.response.use(
// 处理业务错误码
if (data.code === 302) {
// 清除token
TokenManager.removeToken()
// 跳转到登录页
router.replace('/login')
// 处理登录过期(防重复提示)
handleLoginExpired()
return Promise.reject(data)
}
// 其他错误码
if (data.code !== 0 && data.code !== undefined) {
ElMessage.error(data.message || data.msg || '请求失败')
// 检查是否是状态刷新相关的请求,如果是则静默处理某些错误
const url = response.config?.url || ''
const isStatusRefreshRequest = url.includes('/plc/plcStatus') || url.includes('/plc/getModel')
const errorMsg = data.message || data.msg || ''
// 如果是状态刷新请求,且错误消息包含特定关键词,则静默处理
if (isStatusRefreshRequest && (
errorMsg.includes('停止失败') ||
errorMsg.includes('未连接') ||
errorMsg.includes('连接失败') ||
errorMsg.includes('获取状态失败') ||
errorMsg.includes('获取模式失败')
)) {
// 静默处理,不弹出错误提示
console.debug('状态刷新请求错误(已静默处理):', errorMsg)
return Promise.reject(data)
}
ElMessage.error(errorMsg || '请求失败')
return Promise.reject(data)
}
return data
},
(error) => {
// 处理401未授权错误
if (error.response?.status === 401) {
// 处理登录过期(防重复提示)
handleLoginExpired()
return Promise.reject(error)
}
// 检查是否是状态刷新相关的请求
const url = error.config?.url || ''
const isStatusRefreshRequest = url.includes('/plc/plcStatus') || url.includes('/plc/getModel')
const errorMsg = error.response?.data?.message || error.response?.data?.msg || '请求失败'
// 如果是状态刷新请求,且错误消息包含特定关键词,则静默处理
if (isStatusRefreshRequest && (
errorMsg.includes('停止失败') ||
errorMsg.includes('未连接') ||
errorMsg.includes('连接失败') ||
errorMsg.includes('获取状态失败') ||
errorMsg.includes('获取模式失败')
)) {
// 静默处理,不弹出错误提示
console.debug('状态刷新请求错误(已静默处理):', errorMsg)
return Promise.reject(error)
}
// 网络错误或其他错误
ElMessage.error(
error.response?.data?.message ||
error.response?.data?.msg ||
'请求失败'
)
ElMessage.error(errorMsg)
return Promise.reject(error)
}
)

View File

@@ -0,0 +1,56 @@
<template>
<div class="home-container">
<div class="welcome-content">
<h1 class="welcome-title">欢迎使用北京融创智能仪器管理系统</h1>
</div>
</div>
</template>
<script setup lang="ts">
// 首页组件,未来将展示数字孪生监控大屏
</script>
<style scoped>
.home-container {
width: 100%;
height: calc(92vh - 25px);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
}
.welcome-content {
text-align: center;
color: #080101;
padding: 40px;
max-width: 100%;
box-sizing: border-box;
}
.welcome-title {
font-size: 48px;
font-weight: 600;
margin: 0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
letter-spacing: 2px;
line-height: 1.4;
}
.welcome-subtitle {
font-size: 24px;
margin: 0;
opacity: 0.9;
font-weight: 300;
letter-spacing: 1px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.welcome-title {
font-size: 32px;
letter-spacing: 1px;
}
}
</style>

View File

@@ -31,36 +31,84 @@
router
:collapse="false"
>
<el-menu-item index="/home">
<el-icon><component :is="getMenuIcon('首页')" /></el-icon>
<span>首页</span>
</el-menu-item>
<el-sub-menu index="system">
<template #title>
<el-icon><Setting /></el-icon>
<el-icon><component :is="getMenuIcon('系统管理')" /></el-icon>
<span>系统管理</span>
</template>
<el-menu-item index="/user">
<el-icon><User /></el-icon>
<el-icon><component :is="getMenuIcon('用户管理')" /></el-icon>
<span>用户管理</span>
</el-menu-item>
<el-menu-item index="/role">
<el-icon><Avatar /></el-icon>
<el-icon><component :is="getMenuIcon('角色管理')" /></el-icon>
<span>角色管理</span>
</el-menu-item>
<el-menu-item index="/department">
<el-icon><OfficeBuilding /></el-icon>
<el-icon><component :is="getMenuIcon('部门管理')" /></el-icon>
<span>部门管理</span>
</el-menu-item>
<el-menu-item index="/position">
<el-icon><Briefcase /></el-icon>
<el-icon><component :is="getMenuIcon('职位管理')" /></el-icon>
<span>职位管理</span>
</el-menu-item>
<el-menu-item index="/manage-log">
<el-icon><Document /></el-icon>
<el-icon><component :is="getMenuIcon('操作日志管理')" /></el-icon>
<span>操作日志管理</span>
</el-menu-item>
<el-menu-item index="/user-role">
<el-icon><UserFilled /></el-icon>
<el-icon><component :is="getMenuIcon('用户角色管理')" /></el-icon>
<span>用户角色管理</span>
</el-menu-item>
</el-sub-menu>
<el-sub-menu index="island">
<template #title>
<el-icon><component :is="getMenuIcon('业务管理')" /></el-icon>
<span>业务管理</span>
</template>
<el-menu-item index="/plc-devinfo">
<el-icon><component :is="getMenuIcon('PLC设备管理')" /></el-icon>
<span>PLC设备管理</span>
</el-menu-item>
<el-menu-item index="/devparam">
<el-icon><component :is="getMenuIcon('动作参数管理')" /></el-icon>
<span>动作参数管理</span>
</el-menu-item>
<el-menu-item index="/devinfo">
<el-icon><component :is="getMenuIcon('动作管理')" /></el-icon>
<span>动作管理</span>
</el-menu-item>
<el-menu-item index="/island-info">
<el-icon><component :is="getMenuIcon('功能岛管理')" /></el-icon>
<span>功能岛管理</span>
</el-menu-item>
</el-sub-menu>
<el-sub-menu index="flow">
<template #title>
<el-icon><component :is="getMenuIcon('流程管理')" /></el-icon>
<span>流程管理</span>
</template>
<el-menu-item index="/plc-device-control">
<el-icon><component :is="getMenuIcon('PLC设备控制')" /></el-icon>
<span>PLC设备控制</span>
</el-menu-item>
<el-menu-item index="/goods-info">
<el-icon><component :is="getMenuIcon('样品管理')" /></el-icon>
<span>样品管理</span>
</el-menu-item>
<el-menu-item index="/flow-info">
<el-icon><component :is="getMenuIcon('标准流程管理')" /></el-icon>
<span>标准流程管理</span>
</el-menu-item>
<el-menu-item index="/step-info">
<el-icon><component :is="getMenuIcon('流程创建')" /></el-icon>
<span>流程创建</span>
</el-menu-item>
</el-sub-menu>
</el-menu>
</el-aside>
@@ -76,7 +124,26 @@
import { ref, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { User, Setting, Avatar, OfficeBuilding, Briefcase, Document, CaretBottom, UserFilled } from '@element-plus/icons-vue'
import {
User,
Setting,
Avatar,
OfficeBuilding,
Briefcase,
Document,
CaretBottom,
UserFilled,
Grid,
Monitor,
Connection,
List,
EditPen,
Files,
Box,
Operation,
Tools,
House,
} from '@element-plus/icons-vue'
import { useAuthStore } from '@/stores/auth'
const router = useRouter()
@@ -93,6 +160,38 @@ const activeMenu = computed(() => {
return route.path
})
// 根据菜单名称获取图标
const getMenuIcon = (menuName: string) => {
const iconMap: Record<string, any> = {
// 一级
首页: House,
系统管理: Setting,
业务管理: Grid,
流程管理: List,
// 系统管理
用户管理: User,
角色管理: Avatar,
部门管理: OfficeBuilding,
职位管理: Briefcase,
操作日志管理: Document,
用户角色管理: UserFilled,
// 业务管理
PLC设备管理: Connection,
动作参数管理: Tools,
动作管理: Monitor,
功能岛管理: Grid,
// 流程管理
PLC设备控制: Operation,
样品管理: Box,
'标准流程管理': Files, // 文件图标,适合标准流程管理
'流程创建': EditPen,
}
return iconMap[menuName] || EditPen
}
// 处理下拉菜单命令
const handleCommand = (command: string) => {
if (command === 'logout') {
@@ -104,8 +203,8 @@ const handleCommand = (command: string) => {
authStore.removeToken()
localStorage.removeItem('username')
ElMessage.success('退出成功')
// 跳转到登录页(如果有登录页)
// router.push('/login')
// 跳转到登录页
router.push('/login')
})
.catch(() => {})
}
@@ -114,7 +213,7 @@ const handleCommand = (command: string) => {
<style scoped>
.layout-container {
height: 100vh;
height: 98vh;
display: flex;
flex-direction: column;
}

View File

@@ -0,0 +1,317 @@
<template>
<div class="login-container">
<div class="login-box">
<div class="login-header">
<h2>北京融创智能仪器管理系统</h2>
<p>用户登录</p>
</div>
<el-form
ref="loginFormRef"
:model="loginForm"
:rules="loginRules"
class="login-form"
label-width="0"
@keyup.enter="handleLogin"
>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
placeholder="请输入用户名2-16位的字母、数字、下划线或减号"
size="large"
:prefix-icon="User"
clearable
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
size="large"
:prefix-icon="Lock"
show-password
clearable
@keyup.enter="handleLogin"
/>
</el-form-item>
<el-form-item>
<div class="button-row">
<el-button
type="primary"
size="large"
class="login-button"
:loading="loginLoading"
@click="handleLogin"
>
登录
</el-button>
<el-button
size="large"
class="register-button"
@click="showRegisterDialog = true"
>
注册
</el-button>
</div>
</el-form-item>
</el-form>
</div>
<!-- 注册弹窗 -->
<el-dialog
v-model="showRegisterDialog"
title="用户注册"
width="500px"
:close-on-click-modal="false"
>
<el-form
ref="registerFormRef"
:model="registerForm"
:rules="registerRules"
label-width="100px"
>
<el-form-item label="用户名" prop="username">
<el-input
v-model="registerForm.username"
placeholder="2-16位的字母、数字、下划线或减号"
clearable
/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="registerForm.password"
type="password"
placeholder="请输入密码"
show-password
clearable
/>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input
v-model="registerForm.confirmPassword"
type="password"
placeholder="请再次输入密码"
show-password
clearable
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showRegisterDialog = false">取消</el-button>
<el-button
type="primary"
:loading="registerLoading"
@click="handleRegister"
>
注册
</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
import { User, Lock } from '@element-plus/icons-vue'
import { userlogin, userregister } from '@/api/system/user'
import { useAuthStore } from '@/stores/auth'
const router = useRouter()
const authStore = useAuthStore()
// 登录表单
const loginFormRef = ref<FormInstance>()
const loginForm = reactive({
username: '',
password: '',
})
const loginLoading = ref(false)
// 注册表单
const registerFormRef = ref<FormInstance>()
const registerForm = reactive({
username: '',
password: '',
confirmPassword: '',
})
const registerLoading = ref(false)
const showRegisterDialog = ref(false)
// 登录表单验证规则
const loginRules: FormRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{
pattern: /^[a-zA-Z0-9_-]{2,16}$/,
message: '用户名必须是2-16位的字母、数字、下划线或减号',
trigger: 'blur',
},
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
],
}
// 注册表单验证规则
const validateConfirmPassword = (rule: any, value: string, callback: Function) => {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== registerForm.password) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
}
const registerRules: FormRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{
pattern: /^[a-zA-Z0-9_-]{2,16}$/,
message: '用户名必须是2-16位的字母、数字、下划线或减号',
trigger: 'blur',
},
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
],
confirmPassword: [
{ required: true, message: '请再次输入密码', trigger: 'blur' },
{ validator: validateConfirmPassword, trigger: 'blur' },
],
}
// 处理登录
const handleLogin = async () => {
if (!loginFormRef.value) return
await loginFormRef.value.validate(async (valid) => {
if (valid) {
loginLoading.value = true
try {
const response: any = await userlogin(loginForm.username, loginForm.password)
// 根据图片返回的data字段直接是token字符串
if (response.code === 0 && response.data) {
const token = response.data
// 存储token
authStore.setToken(token)
// 存储用户名
localStorage.setItem('username', loginForm.username)
ElMessage.success('登录成功')
// 跳转到首页
router.push('/home')
}
} catch (error: any) {
// 错误提示已在全局响应拦截中处理,这里不重复弹出
console.warn('login error:', error)
} finally {
loginLoading.value = false
}
}
})
}
// 处理注册
const handleRegister = async () => {
if (!registerFormRef.value) return
await registerFormRef.value.validate(async (valid) => {
if (valid) {
registerLoading.value = true
try {
const response: any = await userregister(registerForm.username, registerForm.password)
if (response.code === 0) {
ElMessage.success('注册成功,请登录')
// 关闭注册弹窗
showRegisterDialog.value = false
// 清空注册表单
if (registerFormRef.value) {
registerFormRef.value.resetFields()
}
// 自动填充登录表单的用户名
loginForm.username = registerForm.username
} else {
ElMessage.error(response.message || '注册失败')
}
} catch (error: any) {
ElMessage.error(error.message || '注册失败')
} finally {
registerLoading.value = false
}
}
})
}
</script>
<style scoped>
.login-container {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.login-box {
width: 450px;
padding: 40px;
background: #fff;
border-radius: 10px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
}
.login-header {
text-align: center;
margin-bottom: 30px;
}
.login-header h2 {
margin: 0 0 10px 0;
font-size: 24px;
color: #333;
font-weight: 500;
}
.login-header p {
margin: 0;
font-size: 14px;
color: #999;
}
.login-form {
margin-top: 30px;
}
.login-form :deep(.el-form-item) {
margin-bottom: 20px;
}
.button-row {
width: 100%;
display: flex;
gap: 12px;
}
.login-button,
.register-button {
flex: 1 1 0;
width: auto;
display: block;
}
.login-button {
margin: 0;
}
</style>

View File

@@ -1,5 +1,10 @@
<template>
<div class="dept-page">
<el-breadcrumb separator="/" style="margin-bottom: 16px">
<el-breadcrumb-item><router-link to="/home">首页</router-link></el-breadcrumb-item>
<el-breadcrumb-item>系统管理</el-breadcrumb-item>
<el-breadcrumb-item>部门管理</el-breadcrumb-item>
</el-breadcrumb>
<div class="dept-container">
<!-- 左侧树形控件 -->
<el-card class="tree-card" shadow="never">

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,606 @@
<template>
<div class="plc-devinfo-page">
<el-breadcrumb separator="/" style="margin-bottom: 16px">
<el-breadcrumb-item><router-link to="/home">首页</router-link></el-breadcrumb-item>
<el-breadcrumb-item>业务管理</el-breadcrumb-item>
<el-breadcrumb-item>PLC设备管理</el-breadcrumb-item>
</el-breadcrumb>
<!-- 搜索栏 -->
<el-card class="search-card" shadow="never">
<div class="search-bar">
<el-form :inline="true" :model="queryForm" label-width="100px">
<el-form-item label="设备名称">
<el-input
v-model="queryForm.devName"
placeholder="输入设备名称"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<div class="toolbar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增PLC设备
</el-button>
</div>
</div>
</el-card>
<!-- PLC设备列表 -->
<el-card shadow="never">
<el-table
v-loading="loading"
:data="deviceList"
border
stripe
style="width: 100%"
>
<el-table-column type="index" label="序号" width="60" :index="getIndex" />
<el-table-column prop="devName" label="设备名称" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ row.devName || '暂无' }}
</template>
</el-table-column>
<el-table-column prop="devModel" label="设备型号" min-width="100" show-overflow-tooltip>
<template #default="{ row }">
{{ row.devModel || '暂无' }}
</template>
</el-table-column>
<el-table-column prop="ipAddr" label="IP地址" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ row.ipAddr || '暂无' }}
</template>
</el-table-column>
<el-table-column prop="port" label="端口" width="100" align="center">
<template #default="{ row }">
{{ row.port || '暂无' }}
</template>
</el-table-column>
<el-table-column prop="protocolType" label="协议" min-width="100" show-overflow-tooltip>
<template #default="{ row }">
{{ row.protocolType || '暂无' }}
</template>
</el-table-column>
<el-table-column prop="company" label="公司名" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ row.company || '暂无' }}
</template>
</el-table-column>
<el-table-column prop="devDesc" label="描述" min-width="150" show-overflow-tooltip>
<template #default="{ row }">
{{ row.devDesc || '暂无' }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip>
<template #default="{ row }">
{{ row.remark || '暂无' }}
</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
size="small"
link
@click="handleEdit(row)"
>
<el-icon><Edit /></el-icon>
编辑
</el-button>
<el-button
type="danger"
size="small"
link
@click="handleDelete(row)"
>
<el-icon><Delete /></el-icon>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination">
<el-pagination
v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
background
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</el-card>
<!-- 新增/编辑抽屉 -->
<el-drawer
v-model="drawerVisible"
:title="drawerTitle"
direction="rtl"
size="500px"
:before-close="handleDrawerClose"
>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
label-position="left"
>
<el-form-item label="设备名称" prop="devName">
<el-input
v-model="formData.devName"
placeholder="请输入设备名称"
clearable
/>
</el-form-item>
<el-form-item label="设备型号" prop="devModel">
<el-input
v-model="formData.devModel"
disabled
placeholder="PLC"
/>
</el-form-item>
<el-form-item label="IP地址" prop="ipAddr">
<el-input
v-model="formData.ipAddr"
placeholder="请输入IP地址"
clearable
/>
</el-form-item>
<el-form-item label="端口" prop="port">
<el-input-number
v-model="formData.port"
:min="1"
:max="65535"
placeholder="请输入端口"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="协议" prop="protocolType">
<el-input
v-model="formData.protocolType"
placeholder="请输入协议类型"
clearable
/>
</el-form-item>
<el-form-item label="公司名" prop="company">
<el-input
v-model="formData.company"
placeholder="请输入公司名"
clearable
/>
</el-form-item>
<el-form-item label="描述" prop="devDesc">
<el-input
v-model="formData.devDesc"
type="textarea"
:rows="3"
placeholder="请输入描述"
clearable
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="formData.remark"
type="textarea"
:rows="3"
placeholder="请输入备注"
clearable
/>
</el-form-item>
</el-form>
<template #footer>
<div class="drawer-footer">
<el-button @click="handleDrawerClose">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitting">
确定
</el-button>
</div>
</template>
</el-drawer>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Edit, Delete } from '@element-plus/icons-vue'
import {
devInfoadd,
devInfodel,
devInfoupd,
devInfolist,
devInfobyid,
} from '@/api/tb/devinfo'
// 加载状态
const loading = ref(false)
const submitting = ref(false)
// 设备列表
const deviceList = ref<any[]>([])
const total = ref(0)
// 查询表单
const queryForm = reactive({
devName: '',
})
// 分页
const pagination = reactive({
pageNum: 1,
pageSize: 10,
})
// 抽屉相关
const drawerVisible = ref(false)
const drawerTitle = ref('新增PLC设备')
const isEdit = ref(false)
const formRef = ref()
// 表单数据
const formData = reactive({
id: undefined,
devName: '',
devModel: 'PLC',
ipAddr: '',
port: undefined as number | undefined,
protocolType: '',
company: '',
devDesc: '',
remark: '',
status: 0, // 状态默认为0
runModel: 0, // 运行模式默认为0
})
// IP地址校验函数
const validateIpAddr = (rule: any, value: any, callback: any) => {
if (!value || String(value).trim() === '') {
callback(new Error('请输入IP地址'))
return
}
const ipStr = String(value).trim().toLowerCase()
// 禁止本机IP格式
if (ipStr === 'localhost' || ipStr === '127.0.0.1') {
callback(new Error('不允许使用本机IP地址localhost或127.0.0.1'))
return
}
// IPv4地址格式校验xxx.xxx.xxx.xxx每段0-255
const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
const match = ipStr.match(ipv4Regex)
if (!match) {
callback(new Error('请输入有效的IP地址格式192.168.1.1'))
return
}
// 检查每段数字是否在0-255范围内
for (let i = 1; i <= 4; i++) {
const segment = match[i]
if (!segment) {
callback(new Error('IP地址格式错误'))
return
}
const num = parseInt(segment, 10)
if (num < 0 || num > 255) {
callback(new Error('IP地址每段数字应在0-255范围内'))
return
}
}
// 再次检查是否为127.0.0.1防止用户输入其他格式的127.0.0.1
if (match[1] === '127' && match[2] === '0' && match[3] === '0' && match[4] === '1') {
callback(new Error('不允许使用本机IP地址127.0.0.1'))
return
}
callback()
}
// 表单验证规则
const formRules = {
devName: [
{ required: true, message: '请输入设备名称', trigger: 'blur' },
],
devModel: [
{ required: true, message: '设备型号不能为空', trigger: 'blur' },
],
ipAddr: [
{ required: true, validator: validateIpAddr, trigger: ['blur', 'change'] },
],
port: [
{ required: true, message: '请输入端口', trigger: 'blur' },
{ type: 'number', min: 1, max: 65535, message: '端口范围应在1-65535之间', trigger: 'blur' },
],
protocolType: [
{ required: true, message: '请输入协议类型', trigger: 'blur' },
],
}
// 获取序号
const getIndex = (index: number) => {
return (pagination.pageNum - 1) * pagination.pageSize + index + 1
}
// 获取设备列表只查询devModel为PLC的设备
const getDeviceList = async () => {
try {
loading.value = true
const params: any = {
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
devModel: 'PLC', // 只查询PLC设备
}
// 设备名称模糊查询
if (queryForm.devName) {
params.devName = queryForm.devName
}
const res: any = await devInfolist(params)
const data = res?.data ?? res ?? {}
// 兼容多种返回格式
const recordsFromData =
data.records ||
data.list ||
data.rows ||
data.items ||
(Array.isArray(data.data) ? data.data : undefined)
const records = Array.isArray(recordsFromData)
? recordsFromData
: Array.isArray(data)
? data
: []
// 再次过滤确保只显示PLC设备
const plcRecords = records.filter((item: any) => item.devModel === 'PLC')
const totalValue =
data.total ?? data.count ?? data.totalCount ?? plcRecords.length ?? 0
deviceList.value = plcRecords
total.value = Number(totalValue) || 0
} catch (error) {
console.error('获取PLC设备列表失败:', error)
ElMessage.error('获取PLC设备列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleSearch = () => {
pagination.pageNum = 1
getDeviceList()
}
// 重置查询
const resetSearch = () => {
queryForm.devName = ''
handleSearch()
}
// 分页大小改变
const handleSizeChange = (size: number) => {
pagination.pageSize = size
getDeviceList()
}
// 当前页改变
const handleCurrentChange = (page: number) => {
pagination.pageNum = page
getDeviceList()
}
// 打开新增抽屉
const handleAdd = () => {
isEdit.value = false
drawerTitle.value = '新增PLC设备'
resetForm()
drawerVisible.value = true
}
// 重置表单
const resetForm = () => {
formData.id = undefined
formData.devName = ''
formData.devModel = 'PLC'
formData.ipAddr = ''
formData.port = undefined
formData.protocolType = ''
formData.company = ''
formData.devDesc = ''
formData.remark = ''
formData.status = 0 // 状态默认为0
formData.runModel = 0 // 运行模式默认为0
formRef.value?.clearValidate()
}
// 编辑设备
const handleEdit = async (item: any) => {
try {
loading.value = true
const res: any = await devInfobyid(item.id)
const data = res?.data ?? res ?? {}
formData.id = data.id ?? item.id
formData.devName = data.devName ?? ''
formData.devModel = data.devModel ?? 'PLC'
formData.ipAddr = data.ipAddr ?? ''
formData.port = data.port ?? undefined
formData.protocolType = data.protocolType ?? ''
formData.company = data.company ?? ''
formData.devDesc = data.devDesc ?? ''
formData.remark = data.remark ?? ''
formData.status = data.status ?? 0
formData.runModel = data.runModel ?? 0
isEdit.value = true
drawerTitle.value = '编辑PLC设备'
drawerVisible.value = true
} catch (error) {
console.error('获取PLC设备信息失败:', error)
ElMessage.error('获取PLC设备信息失败')
} finally {
loading.value = false
}
}
// 删除设备
const handleDelete = async (item: any) => {
try {
await ElMessageBox.confirm(
`确定要删除PLC设备"${item.devName || '未命名'}"吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
loading.value = true
const res: any = await devInfodel(item.id)
if (res?.code === '0' || res?.code === 0 || res?.success) {
ElMessage.success('删除成功')
getDeviceList()
} else {
ElMessage.error(res?.message || res?.msg || '删除失败')
}
} catch (error: any) {
if (error !== 'cancel') {
console.error('删除PLC设备失败:', error)
ElMessage.error('删除PLC设备失败')
}
} finally {
loading.value = false
}
}
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
submitting.value = true
// 构建提交数据
const submitData: any = {
devName: formData.devName,
devModel: 'PLC', // 设备型号固定为PLC
ipAddr: formData.ipAddr, // 必填项
port: formData.port, // 必填项
protocolType: formData.protocolType, // 必填项
status: isEdit.value ? formData.status : 0, // 新增时状态为0编辑时使用原值
runModel: isEdit.value ? formData.runModel : 0, // 新增时运行模式为0编辑时使用原值
}
// 添加其他可选字段(如果非空)
if (formData.company) {
submitData.company = formData.company
}
if (formData.devDesc) {
submitData.devDesc = formData.devDesc
}
if (formData.remark) {
submitData.remark = formData.remark
}
let res: any
if (isEdit.value && formData.id) {
// 编辑 - 需要传入ID其他字段可选(非空则更新)
res = await devInfoupd({
id: formData.id,
...submitData,
})
} else {
// 新增
res = await devInfoadd(submitData)
}
if (res?.code === '0' || res?.code === 0 || res?.success) {
ElMessage.success(isEdit.value ? '编辑成功' : '新增成功')
drawerVisible.value = false
getDeviceList()
} else {
ElMessage.error(res?.message || res?.msg || (isEdit.value ? '编辑失败' : '新增失败'))
}
} catch (error: any) {
if (error !== false) {
console.error('提交失败:', error)
ElMessage.error('提交失败,请检查表单')
}
} finally {
submitting.value = false
}
}
// 关闭抽屉
const handleDrawerClose = () => {
drawerVisible.value = false
resetForm()
}
// 初始化
onMounted(() => {
getDeviceList()
})
</script>
<style lang="scss" scoped>
.plc-devinfo-page {
display: flex;
flex-direction: column;
gap: 12px;
}
.search-card {
padding-bottom: 4px;
}
.search-bar {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
.toolbar {
display: flex;
align-items: center;
gap: 8px;
}
.pagination {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
.drawer-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,859 @@
<template>
<div class="island-info-page">
<el-breadcrumb separator="/" style="margin-bottom: 16px">
<el-breadcrumb-item><router-link to="/home">首页</router-link></el-breadcrumb-item>
<el-breadcrumb-item>业务管理</el-breadcrumb-item>
<el-breadcrumb-item>功能岛管理</el-breadcrumb-item>
</el-breadcrumb>
<!-- 搜索栏 -->
<el-card class="search-card" shadow="never">
<div class="search-bar">
<el-form :inline="true" :model="queryForm" label-width="80px">
<el-form-item label="岛屿名称">
<el-input
v-model="queryForm.islandName"
placeholder="输入岛屿名称"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<div class="toolbar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增功能岛
</el-button>
</div>
</div>
</el-card>
<!-- 功能岛列表 -->
<el-card shadow="never">
<div class="island-list" v-loading="loading">
<div v-if="islandList.length === 0 && !loading" class="empty-state">
<el-empty description="暂无功能岛数据" :image-size="100" />
</div>
<div v-else class="island-grid">
<div
v-for="(item, index) in islandList"
:key="item.id"
class="island-card"
:style="{ animationDelay: `${index * 50}ms` }"
>
<!-- 左侧步骤编号和过滤器图标 -->
<div class="card-left">
<div class="step-badge">{{ (pagination.pageNum - 1) * pagination.pageSize + index + 1 }}</div>
<el-icon class="filter-icon">
<Operation />
</el-icon>
</div>
<!-- 中间内容区域 -->
<div class="card-content">
<!-- Logo和名称水平对齐 -->
<div class="island-header">
<el-icon class="logo-icon" :size="32">
<component :is="getIslandIcon(item.islandName || item.name)" />
</el-icon>
<div class="island-name">{{ item.islandName || item.name || '未命名功能岛' }}</div>
</div>
<!-- 功能岛描述 -->
<div class="island-desc">{{ item.desc || item.islandDesc || item.description || '暂无描述' }}</div>
</div>
<!-- 右上角操作菜单 -->
<div class="card-actions">
<el-dropdown trigger="click" @command="(command: string) => handleCommand(command, item)">
<el-icon class="more-icon">
<MoreFilled />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="edit">
<el-icon><Edit /></el-icon>
编辑
</el-dropdown-item>
<el-dropdown-item command="delete" divided>
<el-icon><Delete /></el-icon>
删除
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<el-pagination
v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize"
:page-sizes="[9]"
layout="total, prev, pager, next, jumper"
:total="total"
background
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</el-card>
<!-- 新增/编辑抽屉 -->
<el-drawer
v-model="drawerVisible"
:title="drawerTitle"
direction="rtl"
size="600px"
:before-close="handleDrawerClose"
>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
label-position="left"
>
<el-form-item label="功能岛名称" prop="islandName">
<el-input
v-model="formData.islandName"
placeholder="请输入功能岛名称"
clearable
@input="handleNameInput"
/>
</el-form-item>
<el-form-item label="功能岛编码" prop="islandCode">
<el-input
v-model="formData.islandCode"
placeholder="请输入功能岛编码"
clearable
/>
</el-form-item>
<el-form-item label="功能岛Logo">
<div class="logo-preview">
<el-icon class="preview-icon" :size="60">
<component :is="getIslandIcon(formData.islandName)" />
</el-icon>
<div class="preview-tip">根据功能岛名称自动生成</div>
</div>
</el-form-item>
<el-form-item label="功能岛描述" prop="islandDesc">
<el-input
v-model="formData.islandDesc"
type="textarea"
:rows="4"
placeholder="请输入功能岛描述"
clearable
/>
</el-form-item>
<el-form-item v-if="isEdit" label="动作信息">
<div class="device-list-wrapper">
<el-table
v-if="deviceList.length > 0"
v-loading="deviceLoading"
:data="deviceList"
border
stripe
size="small"
style="width: 100%"
>
<el-table-column type="index" label="序号" width="60" :index="getDeviceIndex" />
<el-table-column prop="devName" label="动作名称" width="180" show-overflow-tooltip>
<template #default="{ row }">
{{ row.devName || '暂无' }}
</template>
</el-table-column>
<el-table-column prop="plcAddr" label="PLC映射地址" width="140" show-overflow-tooltip>
<template #default="{ row }">
{{ row.plcAddr ?? '暂无' }}
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="80" align="center">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)" size="small">
{{ getStatusText(row.status) }}
</el-tag>
</template>
</el-table-column>
</el-table>
<div v-if="deviceList.length === 0 && !deviceLoading" class="no-devices">
该功能岛暂无动作
</div>
</div>
</el-form-item>
</el-form>
<template #footer>
<div class="drawer-footer">
<el-button @click="handleDrawerClose">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitting">
确定
</el-button>
</div>
</template>
</el-drawer>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
Plus,
MoreFilled,
Edit,
Delete,
Operation,
Watermelon,
Sunny,
Connection,
Histogram,
RefreshRight,
Aim,
Goblet,
WindPower,
Filter,
User,
Setting,
Tools,
Box,
Grid,
Monitor,
Cpu,
DataAnalysis,
Upload,
Download,
MagicStick,
TakeawayBox,
CollectionTag,
Position,
Dish,
Bowl,
HomeFilled,
Odometer,
Sunrise,
} from '@element-plus/icons-vue'
import {
islandInfoadd,
islandInfodel,
islandInfoupd,
islandInfolist,
islandInfobyid,
} from '@/api/tb/islandinfo'
import { devselect } from '@/api/tb/devinfo'
// 加载状态
const loading = ref(false)
const submitting = ref(false)
const deviceLoading = ref(false)
// 功能岛列表
const islandList = ref<any[]>([])
const total = ref(0)
// 查询表单
const queryForm = reactive({
islandName: '',
})
// 分页
const pagination = reactive({
pageNum: 1,
pageSize: 9,
})
// 抽屉相关
const drawerVisible = ref(false)
const drawerTitle = ref('新增功能岛')
const isEdit = ref(false)
const formRef = ref()
// 表单数据
const formData = reactive({
id: undefined,
islandName: '',
islandCode: '',
islandDesc: '',
})
// 动作列表相关
const deviceList = ref<any[]>([])
// 表单验证规则
const formRules = {
islandName: [
{ required: true, message: '请输入功能岛名称', trigger: 'blur' },
],
islandCode: [
{ required: true, message: '请输入功能岛编码', trigger: 'blur' },
],
}
// 获取功能岛图标(根据名称自动生成)
const getIslandIcon = (name: string) => {
if (!name) return Box
const nameLower = name.toLowerCase()
// 预设功能岛与唯一图标的映射(避免重复)
const iconRules = [
{ keywords: ['涡旋'], icon: MagicStick },
{ keywords: ['加液', '加样'], icon: Watermelon },
{ keywords: ['进样'], icon: Upload },
{ keywords: ['分液'], icon: TakeawayBox },
{ keywords: ['浓缩'], icon: CollectionTag },
{ keywords: ['移上清'], icon: Position },
{ keywords: ['取液'], icon: Dish },
{ keywords: ['金属浴', '金属'], icon: Bowl },
{ keywords: ['干燥', '硫酸钠'], icon: Sunrise },
{ keywords: ['温室', '静置'], icon: HomeFilled },
{ keywords: ['ph', '酸碱'], icon: Odometer },
{ keywords: ['出样'], icon: Download },
{ keywords: ['水浴', '恒温'], icon: Sunny },
{ keywords: ['震荡'], icon: Connection },
{ keywords: ['超声'], icon: Histogram },
{ keywords: ['离心'], icon: RefreshRight },
{ keywords: ['移液'], icon: Aim },
{ keywords: ['萃取'], icon: Goblet },
{ keywords: ['氮吹'], icon: WindPower },
{ keywords: ['过膜', '过滤'], icon: Filter },
{ keywords: ['人工'], icon: User },
{ keywords: ['系统', '管理'], icon: Setting },
{ keywords: ['工具'], icon: Tools },
{ keywords: ['数据', '分析'], icon: DataAnalysis },
{ keywords: ['监控', '显示'], icon: Monitor },
{ keywords: ['处理', '计算'], icon: Cpu },
{ keywords: ['网格', '布局'], icon: Grid },
]
const matched = iconRules.find(rule =>
rule.keywords.some(keyword => nameLower.includes(keyword.toLowerCase()))
)
return matched ? matched.icon : Box
}
// 处理名称输入
const handleNameInput = () => {
// 名称输入时Logo会自动更新通过getIslandIcon函数
}
// 获取状态类型
const getStatusType = (status: number | null | undefined) => {
if (status === 0) return 'success' // 空闲 - 绿色
if (status === 1) return 'warning' // 运行 - 黄色
if (status === 4) return 'danger' // 故障 - 红色
return 'info'
}
// 获取状态文本
const getStatusText = (status: number | null | undefined) => {
if (status === 0) return '空闲'
if (status === 1) return '运行'
if (status === 4) return '故障'
return '未知'
}
// 获取动作序号
const getDeviceIndex = (index: number) => {
return index + 1
}
// 获取功能岛列表
const getIslandList = async () => {
try {
loading.value = true
const res: any = await islandInfolist({
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
...queryForm,
})
const data = res?.data ?? res ?? {}
// 兼容多种返回格式:{data:{records,total}} | {records,total} | {data:[]} | []
const recordsFromData =
data.records ||
data.list ||
data.rows ||
data.items ||
(Array.isArray(data.data) ? data.data : undefined)
const records = Array.isArray(recordsFromData)
? recordsFromData
: Array.isArray(data)
? data
: []
const totalValue =
data.total ?? data.count ?? data.totalCount ?? records.length ?? 0
islandList.value = records
total.value = Number(totalValue) || 0
} catch (error) {
console.error('获取功能岛列表失败:', error)
ElMessage.error('获取功能岛列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleSearch = () => {
pagination.pageNum = 1
getIslandList()
}
// 重置查询
const resetSearch = () => {
queryForm.islandName = ''
handleSearch()
}
// 分页大小改变
const handleSizeChange = (size: number) => {
pagination.pageSize = size
getIslandList()
}
// 当前页改变
const handleCurrentChange = (page: number) => {
pagination.pageNum = page
getIslandList()
}
// 打开新增抽屉
const handleAdd = () => {
isEdit.value = false
drawerTitle.value = '新增功能岛'
resetForm()
drawerVisible.value = true
}
// 重置表单
const resetForm = () => {
formData.id = undefined
formData.islandName = ''
formData.islandCode = ''
formData.islandDesc = ''
deviceList.value = []
formRef.value?.clearValidate()
}
// 处理操作命令
const handleCommand = async (command: string, item: any) => {
if (command === 'edit') {
await handleEdit(item)
} else if (command === 'delete') {
await handleDelete(item)
}
}
// 编辑功能岛
const handleEdit = async (item: any) => {
try {
loading.value = true
const res: any = await islandInfobyid(item.id)
if (res.code === '0' || res.code === 0) {
const data = res.data || res || item
formData.id = data.id ?? item.id
formData.islandName = data.islandName ?? data.name ?? ''
formData.islandCode = data.islandCode ?? data.code ?? ''
// 优先使用后端的 desc 字段,兼容其他可能的字段名
formData.islandDesc = data.desc ?? data.islandDesc ?? data.description ?? ''
// 加载该功能岛下的动作列表
if (formData.id) {
await loadDeviceList(formData.id)
}
isEdit.value = true
drawerTitle.value = '编辑功能岛'
drawerVisible.value = true
} else {
ElMessage.error(res.message || res.msg || '获取功能岛信息失败')
}
} catch (error) {
console.error('获取功能岛信息失败:', error)
ElMessage.error('获取功能岛信息失败')
} finally {
loading.value = false
}
}
// 加载该功能岛下的动作列表
const loadDeviceList = async (islandId: number | string) => {
try {
deviceLoading.value = true
const res: any = await devselect({})
const allDevices = res?.data ?? res ?? []
if (Array.isArray(allDevices)) {
// 过滤出该功能岛下的动作并排除PLC动作
deviceList.value = allDevices.filter((device: any) => {
const devModel = device.devModel || ''
const isNotPlc = devModel.toUpperCase() !== 'PLC'
const isBelongToIsland = device.islandId && (device.islandId === islandId || device.islandId === String(islandId))
return isNotPlc && isBelongToIsland
})
} else {
deviceList.value = []
}
} catch (error) {
console.error('加载动作列表失败:', error)
deviceList.value = []
} finally {
deviceLoading.value = false
}
}
// 删除功能岛
const handleDelete = async (item: any) => {
try {
// 检查是否绑定了动作
const res: any = await devselect({})
const allDevices = res?.data ?? res ?? []
const boundDevicesList = Array.isArray(allDevices)
? allDevices.filter((device: any) => {
const devModel = device.devModel || ''
const isNotPlc = devModel.toUpperCase() !== 'PLC'
const isBelongToIsland = device.islandId && (device.islandId === item.id || device.islandId === String(item.id))
return isNotPlc && isBelongToIsland
})
: []
if (boundDevicesList.length > 0) {
const deviceNames = boundDevicesList.map((d: any) => d.devName || '未命名动作').join('、')
const islandName = item.islandName || item.name || '未命名'
await ElMessageBox.alert(
`${islandName}下还有${deviceNames}${boundDevicesList.length}个动作,请先完成动作解绑`,
'提示',
{
confirmButtonText: '确定',
type: 'warning',
}
)
return
}
await ElMessageBox.confirm(
`确定要删除功能岛"${item.islandName || item.name || '未命名'}"吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
loading.value = true
const deleteRes: any = await islandInfodel(item.id)
if (deleteRes?.code === '0' || deleteRes?.code === 0) {
ElMessage.success('删除成功')
getIslandList()
} else {
ElMessage.error(deleteRes?.message || deleteRes?.msg || '删除失败')
}
} catch (error: any) {
if (error !== 'cancel') {
console.error('删除功能岛失败:', error)
ElMessage.error('删除功能岛失败')
}
} finally {
loading.value = false
}
}
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
submitting.value = true
// 根据后端API要求将字段映射为正确的名称
const submitData: any = {
islandName: formData.islandName,
islandCode: formData.islandCode,
desc: formData.islandDesc || '', // 描述字段映射为 desc
}
let res: any
if (isEdit.value && formData.id) {
// 编辑 - 需要传入ID其他字段可选(非空则更新)
res = await islandInfoupd({
id: formData.id,
...submitData,
})
} else {
// 新增
res = await islandInfoadd(submitData)
}
if (res.code === '0' || res.code === 0) {
ElMessage.success(isEdit.value ? '编辑成功' : '新增成功')
drawerVisible.value = false
getIslandList()
} else {
ElMessage.error(res.message || res.msg || (isEdit.value ? '编辑失败' : '新增失败'))
}
} catch (error: any) {
if (error !== false) {
console.error('提交失败:', error)
ElMessage.error('提交失败,请检查表单')
}
} finally {
submitting.value = false
}
}
// 关闭抽屉
const handleDrawerClose = () => {
drawerVisible.value = false
resetForm()
}
// 初始化
onMounted(() => {
getIslandList()
})
</script>
<style lang="scss" scoped>
.island-info-page {
display: flex;
flex-direction: column;
gap: 12px;
}
.search-card {
padding-bottom: 4px;
}
.search-bar {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
.toolbar {
display: flex;
align-items: center;
gap: 8px;
}
.island-list {
min-height: 400px;
}
.pagination {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
.empty-state {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
background-color: #fff;
border-radius: 4px;
}
.island-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
gap: 20px;
padding: 20px;
background: linear-gradient(135deg, #e3f2fd 0%, #f0f2f5 100%);
border-radius: 4px;
min-height: 400px;
background-image:
linear-gradient(rgba(64, 158, 255, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(64, 158, 255, 0.03) 1px, transparent 1px);
background-size: 20px 20px;
}
.island-card {
position: relative;
background-color: #fff;
border-radius: 12px;
padding: 16px 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s;
display: flex;
align-items: flex-start;
min-height: 95px;
height: 95px;
border: 1px solid rgba(64, 158, 255, 0.1);
animation: slideUpIn 0.4s ease-out forwards;
opacity: 0;
&:hover {
box-shadow: 0 4px 16px rgba(64, 158, 255, 0.2);
transform: translateY(-4px);
border-color: rgba(64, 158, 255, 0.3);
}
}
.card-left {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 15px;
.step-badge {
width: 32px;
height: 32px;
background-color: #409eff;
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 500;
margin-bottom: 10px;
}
.filter-icon {
font-size: 32px;
width: 32px;
height: 32px;
color: #909399;
cursor: pointer;
transition: color 0.3s;
&:hover {
color: #409eff;
}
}
}
.card-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-start;
padding-left: 8px;
padding-top: 8px; // 向下平移使Logo和岛名位置在序号与序号下图标中间
.island-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 0;
.logo-icon {
color: #409eff;
flex-shrink: 0;
}
.island-name {
font-size: 22px;
font-weight: 700;
color: #303133;
line-height: 1.5;
word-break: break-all;
flex: 1;
}
}
.island-desc {
font-size: 16px;
color: #606266;
line-height: 1.5;
word-break: break-all;
margin-top: calc(22px * 1); // 岛名字体22px * 1倍行距 = 22px
padding-left: 0; // 第一个字与Logo图垂直对齐Logo在island-header中描述独立所以padding-left为0即可对齐
display: -webkit-box;
-webkit-line-clamp: 1;
line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
}
.card-actions {
position: absolute;
top: 10px;
right: 10px;
.more-icon {
font-size: 18px;
color: #909399;
cursor: pointer;
transition: color 0.3s;
padding: 4px;
&:hover {
color: #409eff;
}
}
}
.logo-preview {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
background-color: #f5f7fa;
border-radius: 4px;
border: 1px dashed #dcdfe6;
.preview-icon {
color: #409eff;
margin-bottom: 10px;
}
.preview-tip {
font-size: 12px;
color: #909399;
}
}
.drawer-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
.device-list-wrapper {
width: 100%;
}
.no-devices {
margin-top: 12px;
padding: 12px;
text-align: center;
color: #909399;
font-size: 14px;
background-color: #f5f7fa;
border-radius: 4px;
border: 1px dashed #dcdfe6;
}
// 功能岛卡片动画:从下往上滑动
@keyframes slideUpIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@@ -1,5 +1,10 @@
<template>
<div class="manage-log-page">
<el-breadcrumb separator="/" style="margin-bottom: 16px">
<el-breadcrumb-item><router-link to="/home">首页</router-link></el-breadcrumb-item>
<el-breadcrumb-item>系统管理</el-breadcrumb-item>
<el-breadcrumb-item>操作日志管理</el-breadcrumb-item>
</el-breadcrumb>
<el-card class="search-card" shadow="never">
<div class="search-bar">
<el-form :inline="true" :model="queryForm" label-width="90px">

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,10 @@
<template>
<div class="position-page">
<el-breadcrumb separator="/" style="margin-bottom: 16px">
<el-breadcrumb-item><router-link to="/home">首页</router-link></el-breadcrumb-item>
<el-breadcrumb-item>系统管理</el-breadcrumb-item>
<el-breadcrumb-item>职位管理</el-breadcrumb-item>
</el-breadcrumb>
<el-card class="search-card" shadow="never">
<div class="search-bar">
<el-form :inline="true" :model="queryForm" label-width="80px">

View File

@@ -1,5 +1,10 @@
<template>
<div class="role-page">
<el-breadcrumb separator="/" style="margin-bottom: 16px">
<el-breadcrumb-item><router-link to="/home">首页</router-link></el-breadcrumb-item>
<el-breadcrumb-item>系统管理</el-breadcrumb-item>
<el-breadcrumb-item>角色管理</el-breadcrumb-item>
</el-breadcrumb>
<el-card class="search-card" shadow="never">
<div class="search-bar">
<el-form :inline="true" :model="queryForm" label-width="80px">

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,10 @@
<template>
<div class="user-role-page">
<el-breadcrumb separator="/" style="margin-bottom: 16px">
<el-breadcrumb-item><router-link to="/home">首页</router-link></el-breadcrumb-item>
<el-breadcrumb-item>系统管理</el-breadcrumb-item>
<el-breadcrumb-item>用户角色管理</el-breadcrumb-item>
</el-breadcrumb>
<el-card class="search-card" shadow="never">
<div class="search-bar">
<el-form :inline="true" :model="queryForm" label-width="80px">

View File

@@ -1,5 +1,10 @@
<template>
<div class="user-page">
<el-breadcrumb separator="/" style="margin-bottom: 16px">
<el-breadcrumb-item><router-link to="/home">首页</router-link></el-breadcrumb-item>
<el-breadcrumb-item>系统管理</el-breadcrumb-item>
<el-breadcrumb-item>用户管理</el-breadcrumb-item>
</el-breadcrumb>
<el-card class="search-card" shadow="never">
<div class="search-bar">
<el-form :inline="true" :model="queryForm" label-width="80px">

View File

@@ -15,4 +15,8 @@ export default defineConfig({
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
server: {
host: '0.0.0.0', // 允许外部访问
port: 5173, // 端口号
},
})