#
Junjie
5 小时以前 aa710969e00e9d7e56a276066a239f74d5c49310
#
10个文件已添加
11个文件已修改
2713 ■■■■■ 已修改文件
src/main/java/com/zy/asrs/controller/BasStationErrLogController.java 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/WrkMastLogController.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/BasStationErrLog.java 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/WrkAnalysis.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/mapper/BasStationErrLogMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/BasStationErrLogService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/BasStationErrLogServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/WrkAnalysisServiceImpl.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/enums/RedisKeyType.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/ZyStationThread.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/ZyStationV3Thread.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/ZyStationV4Thread.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/v5/StationV5StatusReader.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/support/StationErrLogSupport.java 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/i18n/en-US/legacy.properties 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/i18n/zh-CN/legacy.properties 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/BasStationErrLogMapper.xml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/sql/20260331_add_station_err_log.sql 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/basStationErrLog/basStationErrLog.js 1165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/wrkMastLog/wrkMastLog.js 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/basStationErrLog/basStationErrLog.html 670 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/BasStationErrLogController.java
New file
@@ -0,0 +1,129 @@
package com.zy.asrs.controller;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.annotations.ManagerAuth;
import com.core.common.BaseRes;
import com.core.common.Cools;
import com.core.common.DateUtils;
import com.core.common.R;
import com.zy.asrs.entity.BasStationErrLog;
import com.zy.asrs.service.BasStationErrLogService;
import com.zy.common.web.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
public class BasStationErrLogController extends BaseController {
    @Autowired
    private BasStationErrLogService basStationErrLogService;
    @RequestMapping(value = "/basStationErrLog/{id}/auth")
    @ManagerAuth
    public R get(@PathVariable("id") String id) {
        return R.ok(basStationErrLogService.getById(String.valueOf(id)));
    }
    @RequestMapping(value = "/basStationErrLog/list/auth")
    @ManagerAuth
    public R list(@RequestParam(defaultValue = "1") Integer curr,
                  @RequestParam(defaultValue = "10") Integer limit,
                  @RequestParam(required = false) String orderByField,
                  @RequestParam(required = false) String orderByType,
                  @RequestParam(required = false) String condition,
                  @RequestParam Map<String, Object> param) {
        QueryWrapper<BasStationErrLog> wrapper = new QueryWrapper<>();
        excludeTrash(param);
        convert(param, wrapper);
        allLike(BasStationErrLog.class, param.keySet(), wrapper, condition);
        if (!Cools.isEmpty(orderByField)) {
            wrapper.orderBy(true, "asc".equals(orderByType), humpToLine(orderByField));
        }
        return R.ok(basStationErrLogService.page(new Page<>(curr, limit), wrapper));
    }
    private <T> void convert(Map<String, Object> map, QueryWrapper<T> wrapper) {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String val = String.valueOf(entry.getValue());
            String column = humpToLine(entry.getKey());
            if (val.contains(RANGE_TIME_LINK)) {
                String[] dates = val.split(RANGE_TIME_LINK);
                wrapper.ge(column, DateUtils.convert(dates[0]));
                wrapper.le(column, DateUtils.convert(dates[1]));
            } else {
                wrapper.like(column, val);
            }
        }
    }
    @RequestMapping(value = "/basStationErrLog/add/auth")
    @ManagerAuth
    public R add(BasStationErrLog basStationErrLog) {
        basStationErrLogService.save(basStationErrLog);
        return R.ok();
    }
    @RequestMapping(value = "/basStationErrLog/update/auth")
    @ManagerAuth
    public R update(BasStationErrLog basStationErrLog) {
        if (Cools.isEmpty(basStationErrLog) || basStationErrLog.getId() == null) {
            return R.error();
        }
        basStationErrLogService.updateById(basStationErrLog);
        return R.ok();
    }
    @RequestMapping(value = "/basStationErrLog/delete/auth")
    @ManagerAuth
    public R delete(@RequestParam(value = "ids[]") Integer[] ids) {
        for (Integer id : ids) {
            basStationErrLogService.removeById(id);
        }
        return R.ok();
    }
    @RequestMapping(value = "/basStationErrLog/export/auth")
    @ManagerAuth
    public R export(@RequestBody JSONObject param) {
        QueryWrapper<BasStationErrLog> wrapper = new QueryWrapper<>();
        List<String> fields = JSONObject.parseArray(param.getJSONArray("fields").toJSONString(), String.class);
        Map<String, Object> map = excludeTrash(param.getJSONObject("basStationErrLog"));
        convert(map, wrapper);
        List<BasStationErrLog> list = basStationErrLogService.list(wrapper);
        return R.ok(exportSupport(list, fields));
    }
    @RequestMapping(value = "/basStationErrLogQuery/auth")
    @ManagerAuth
    public R query(String condition) {
        QueryWrapper<BasStationErrLog> wrapper = new QueryWrapper<>();
        wrapper.like("id", condition);
        Page<BasStationErrLog> page = basStationErrLogService.page(new Page<>(0, 10), wrapper);
        List<Map<String, Object>> result = new ArrayList<>();
        for (BasStationErrLog basStationErrLog : page.getRecords()) {
            Map<String, Object> map = new HashMap<>();
            map.put("id", basStationErrLog.getId());
            map.put("value", basStationErrLog.getId());
            result.add(map);
        }
        return R.ok(result);
    }
    @RequestMapping(value = "/basStationErrLog/check/column/auth")
    @ManagerAuth
    public R query(@RequestBody JSONObject param) {
        QueryWrapper<BasStationErrLog> wrapper = new QueryWrapper<BasStationErrLog>()
                .eq(humpToLine(String.valueOf(param.get("key"))), param.get("val"));
        if (null != basStationErrLogService.getOne(wrapper)) {
            return R.parse(BaseRes.REPEAT).add(getComment(BasStationErrLog.class, String.valueOf(param.get("key"))));
        }
        return R.ok();
    }
}
src/main/java/com/zy/asrs/controller/WrkMastLogController.java
@@ -17,12 +17,22 @@
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@RestController
public class WrkMastLogController extends BaseController {
    private static final Set<String> EXACT_SEARCH_COLUMNS = new HashSet<>(Arrays.asList(
            "wrk_sts",
            "io_type",
            "sta_no",
            "source_sta_no"
    ));
    @Autowired
    private WrkMastLogService wrkMastLogService;
@@ -65,10 +75,10 @@
                wrapper.ge(column, DateUtils.convert(dates[0]));
                wrapper.le(column, DateUtils.convert(dates[1]));
            } else {
                if ("manu_type".equals(column)) {
                    wrapper.like(column, val);
                } else {
                if (EXACT_SEARCH_COLUMNS.contains(column)) {
                    wrapper.eq(column, val);
                } else {
                    wrapper.like(column, val);
                }
            }
        }
src/main/java/com/zy/asrs/entity/BasStationErrLog.java
New file
@@ -0,0 +1,211 @@
package com.zy.asrs.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.Cools;
import com.core.common.SpringUtils;
import com.zy.asrs.service.BasStationService;
import com.zy.asrs.service.BasWrkIotypeService;
import com.zy.asrs.service.BasWrkStatusService;
import com.zy.system.entity.User;
import com.zy.system.service.UserService;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
@Data
@TableName("asr_bas_station_err_log")
public class BasStationErrLog implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "编号")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "工作号")
    @TableField("wrk_no")
    private Integer wrkNo;
    @ApiModelProperty(value = "发生时间")
    @TableField("start_time")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date startTime;
    @ApiModelProperty(value = "结束时间")
    @TableField("end_time")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date endTime;
    @ApiModelProperty(value = "工作状态")
    @TableField("wrk_sts")
    private Long wrkSts;
    @ApiModelProperty(value = "入出库类型")
    @TableField("io_type")
    private Integer ioType;
    @ApiModelProperty(value = "站点号")
    @TableField("station_id")
    private Integer stationId;
    @ApiModelProperty(value = "目标库位")
    @TableField("loc_no")
    private String locNo;
    @ApiModelProperty(value = "目标站")
    @TableField("sta_no")
    private Integer staNo;
    @ApiModelProperty(value = "源站")
    @TableField("source_sta_no")
    private Integer sourceStaNo;
    @ApiModelProperty(value = "源库位")
    @TableField("source_loc_no")
    private String sourceLocNo;
    @ApiModelProperty(value = "条码")
    private String barcode;
    @ApiModelProperty(value = "异常码")
    @TableField("err_code")
    private Integer errCode;
    @ApiModelProperty(value = "异常")
    private String error;
    @ApiModelProperty(value = "异常情况 1: 未处理  2: 已修复")
    private Integer status;
    @ApiModelProperty(value = "添加时间")
    @TableField("create_time")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    @ApiModelProperty(value = "添加人员")
    @TableField("create_by")
    private Long createBy;
    @ApiModelProperty(value = "修改时间")
    @TableField("update_time")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updateTime;
    @ApiModelProperty(value = "修改人员")
    @TableField("update_by")
    private Long updateBy;
    @ApiModelProperty(value = "备注")
    private String memo;
    @ApiModelProperty(value = "系统状态数据")
    @TableField("system_status")
    private String systemStatus;
    public String getStartTime$() {
        return formatDate(startTime);
    }
    public String getEndTime$() {
        return formatDate(endTime);
    }
    public String getWrkSts$() {
        if (wrkSts == null) {
            return null;
        }
        BasWrkStatusService service = SpringUtils.getBean(BasWrkStatusService.class);
        if (service == null) {
            return String.valueOf(wrkSts);
        }
        BasWrkStatus basWrkStatus = service.getById(wrkSts);
        return basWrkStatus == null ? String.valueOf(wrkSts) : basWrkStatus.getWrkDesc();
    }
    public String getIoType$() {
        if (ioType == null) {
            return null;
        }
        BasWrkIotypeService service = SpringUtils.getBean(BasWrkIotypeService.class);
        if (service == null) {
            return String.valueOf(ioType);
        }
        BasWrkIotype basWrkIotype = service.getById(ioType);
        return basWrkIotype == null ? String.valueOf(ioType) : basWrkIotype.getIoDesc();
    }
    public String getStationLabel$() {
        if (stationId == null) {
            return null;
        }
        BasStationService service = SpringUtils.getBean(BasStationService.class);
        if (service == null) {
            return String.valueOf(stationId);
        }
        BasStation basStation = service.getById(stationId);
        if (basStation == null || Cools.isEmpty(basStation.getStationAlias())) {
            return String.valueOf(stationId);
        }
        return stationId + " - " + basStation.getStationAlias();
    }
    public String getStatus$() {
        if (status == null) {
            return null;
        }
        switch (status) {
            case 1:
                return "未处理";
            case 2:
                return "已修复";
            default:
                return String.valueOf(status);
        }
    }
    public String getCreateTime$() {
        return formatDate(createTime);
    }
    public String getCreateBy$() {
        if (createBy == null) {
            return null;
        }
        UserService service = SpringUtils.getBean(UserService.class);
        if (service == null) {
            return String.valueOf(createBy);
        }
        User user = service.getById(createBy);
        return user == null ? String.valueOf(createBy) : String.valueOf(user.getId());
    }
    public String getUpdateTime$() {
        return formatDate(updateTime);
    }
    public String getUpdateBy$() {
        if (updateBy == null) {
            return null;
        }
        UserService service = SpringUtils.getBean(UserService.class);
        if (service == null) {
            return String.valueOf(updateBy);
        }
        User user = service.getById(updateBy);
        return user == null ? String.valueOf(updateBy) : String.valueOf(user.getId());
    }
    private String formatDate(Date value) {
        if (Cools.isEmpty(value)) {
            return "";
        }
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(value);
    }
}
src/main/java/com/zy/asrs/entity/WrkAnalysis.java
@@ -139,6 +139,14 @@
    @TableField("rgv_fault_duration_ms")
    private Long rgvFaultDurationMs;
    @ApiModelProperty(value = "输送站点故障次数")
    @TableField("station_fault_count")
    private Integer stationFaultCount;
    @ApiModelProperty(value = "输送站点故障耗时毫秒")
    @TableField("station_fault_duration_ms")
    private Long stationFaultDurationMs;
    @ApiModelProperty(value = "数据完整性")
    @TableField("metric_completeness")
    private String metricCompleteness;
src/main/java/com/zy/asrs/mapper/BasStationErrLogMapper.java
New file
@@ -0,0 +1,9 @@
package com.zy.asrs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zy.asrs.entity.BasStationErrLog;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BasStationErrLogMapper extends BaseMapper<BasStationErrLog> {
}
src/main/java/com/zy/asrs/service/BasStationErrLogService.java
New file
@@ -0,0 +1,7 @@
package com.zy.asrs.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zy.asrs.entity.BasStationErrLog;
public interface BasStationErrLogService extends IService<BasStationErrLog> {
}
src/main/java/com/zy/asrs/service/impl/BasStationErrLogServiceImpl.java
New file
@@ -0,0 +1,11 @@
package com.zy.asrs.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zy.asrs.entity.BasStationErrLog;
import com.zy.asrs.mapper.BasStationErrLogMapper;
import com.zy.asrs.service.BasStationErrLogService;
import org.springframework.stereotype.Service;
@Service("basStationErrLogService")
public class BasStationErrLogServiceImpl extends ServiceImpl<BasStationErrLogMapper, BasStationErrLog> implements BasStationErrLogService {
}
src/main/java/com/zy/asrs/service/impl/WrkAnalysisServiceImpl.java
@@ -46,6 +46,7 @@
    private final BasCrnpErrLogService basCrnpErrLogService;
    private final BasDualCrnpErrLogService basDualCrnpErrLogService;
    private final BasRgvErrLogService basRgvErrLogService;
    private final BasStationErrLogService basStationErrLogService;
    private final BasStationService basStationService;
    private final BasWrkStatusService basWrkStatusService;
    private final WrkMastService wrkMastService;
@@ -54,6 +55,7 @@
                                  BasCrnpErrLogService basCrnpErrLogService,
                                  BasDualCrnpErrLogService basDualCrnpErrLogService,
                                  BasRgvErrLogService basRgvErrLogService,
                                  BasStationErrLogService basStationErrLogService,
                                  BasStationService basStationService,
                                  BasWrkStatusService basWrkStatusService,
                                  WrkMastService wrkMastService) {
@@ -61,6 +63,7 @@
        this.basCrnpErrLogService = basCrnpErrLogService;
        this.basDualCrnpErrLogService = basDualCrnpErrLogService;
        this.basRgvErrLogService = basRgvErrLogService;
        this.basStationErrLogService = basStationErrLogService;
        this.basStationService = basStationService;
        this.basWrkStatusService = basWrkStatusService;
        this.wrkMastService = wrkMastService;
@@ -89,6 +92,8 @@
        entity.setDualCrnFaultDurationMs(defaultLong(entity.getDualCrnFaultDurationMs()));
        entity.setRgvFaultCount(defaultInt(entity.getRgvFaultCount()));
        entity.setRgvFaultDurationMs(defaultLong(entity.getRgvFaultDurationMs()));
        entity.setStationFaultCount(defaultInt(entity.getStationFaultCount()));
        entity.setStationFaultDurationMs(defaultLong(entity.getStationFaultDurationMs()));
        entity.setMetricCompleteness(METRIC_PARTIAL);
        entity.setUpdateTime(now);
        this.saveOrUpdate(entity);
@@ -243,6 +248,8 @@
        entity.setDualCrnFaultDurationMs(faultSummary.dualDurationMs);
        entity.setRgvFaultCount(faultSummary.rgvCount);
        entity.setRgvFaultDurationMs(faultSummary.rgvDurationMs);
        entity.setStationFaultCount(faultSummary.stationCount);
        entity.setStationFaultDurationMs(faultSummary.stationDurationMs);
        entity.setMetricCompleteness(resolveMetricCompleteness(wrkMast, entity));
        entity.setUpdateTime(time);
        this.saveOrUpdate(entity);
@@ -610,6 +617,7 @@
            addDeviceFaultDuration(durationMap, "单堆垛机", item.getCrnNo(), item.getCrnFaultDurationMs());
            addDeviceFaultDuration(durationMap, "双工位堆垛机", item.getDualCrnNo(), item.getDualCrnFaultDurationMs());
            addDeviceFaultDuration(durationMap, "RGV", item.getRgvNo(), item.getRgvFaultDurationMs());
            addDeviceFaultDuration(durationMap, "输送站点", null, item.getStationFaultDurationMs());
        }
        List<Map<String, Object>> result = new ArrayList<>();
        durationMap.forEach((name, durationMs) -> result.add(slice(name, durationMs)));
@@ -680,6 +688,7 @@
        target.put("crnFaultDurationMs", analysis == null ? 0L : defaultLong(analysis.getCrnFaultDurationMs()));
        target.put("dualCrnFaultDurationMs", analysis == null ? 0L : defaultLong(analysis.getDualCrnFaultDurationMs()));
        target.put("rgvFaultDurationMs", analysis == null ? 0L : defaultLong(analysis.getRgvFaultDurationMs()));
        target.put("stationFaultDurationMs", analysis == null ? 0L : defaultLong(analysis.getStationFaultDurationMs()));
        target.put("metricCompleteness", analysis == null ? METRIC_PARTIAL : analysis.getMetricCompleteness());
    }
@@ -691,14 +700,17 @@
        List<BasCrnpErrLog> crnList = basCrnpErrLogService.list(new QueryWrapper<BasCrnpErrLog>().eq("wrk_no", wrkNo));
        List<BasDualCrnpErrLog> dualList = basDualCrnpErrLogService.list(new QueryWrapper<BasDualCrnpErrLog>().eq("wrk_no", wrkNo));
        List<BasRgvErrLog> rgvList = basRgvErrLogService.list(new QueryWrapper<BasRgvErrLog>().eq("task_no", wrkNo));
        List<BasStationErrLog> stationList = basStationErrLogService.list(new QueryWrapper<BasStationErrLog>().eq("wrk_no", wrkNo));
        summary.crnCount = crnList.size();
        summary.crnDurationMs = durationMs(crnList, finishTime);
        summary.dualCount = dualList.size();
        summary.dualDurationMs = durationMs(dualList, finishTime);
        summary.rgvCount = rgvList.size();
        summary.rgvDurationMs = durationMs(rgvList, finishTime);
        summary.totalCount = summary.crnCount + summary.dualCount + summary.rgvCount;
        summary.totalDurationMs = summary.crnDurationMs + summary.dualDurationMs + summary.rgvDurationMs;
        summary.stationCount = stationList.size();
        summary.stationDurationMs = durationMs(stationList, finishTime);
        summary.totalCount = summary.crnCount + summary.dualCount + summary.rgvCount + summary.stationCount;
        summary.totalDurationMs = summary.crnDurationMs + summary.dualDurationMs + summary.rgvDurationMs + summary.stationDurationMs;
        summary.hasFault = summary.totalCount > 0 ? 1 : 0;
        return summary;
    }
@@ -717,6 +729,9 @@
            } else if (item instanceof BasRgvErrLog) {
                startTime = ((BasRgvErrLog) item).getStartTime();
                endTime = ((BasRgvErrLog) item).getEndTime();
            } else if (item instanceof BasStationErrLog) {
                startTime = ((BasStationErrLog) item).getStartTime();
                endTime = ((BasStationErrLog) item).getEndTime();
            }
            if (startTime == null) {
                continue;
@@ -977,6 +992,8 @@
        private long dualDurationMs;
        private int rgvCount;
        private long rgvDurationMs;
        private int stationCount;
        private long stationDurationMs;
    }
    private static class BucketAccumulator {
src/main/java/com/zy/core/enums/RedisKeyType.java
@@ -24,6 +24,7 @@
    DEVICE_ERR_ACTIVE_RGV("device_err_active_rgv_"),
    DEVICE_ERR_ACTIVE_CRN("device_err_active_crn_"),
    DEVICE_ERR_ACTIVE_DUAL_CRN("device_err_active_dual_crn_"),
    DEVICE_ERR_ACTIVE_STATION("device_err_active_station_"),
    DEVICE_STATION_MOVE_RESET("device_station_move_reset_"),
    CRN_SEND_COMMAND_LOCK("crn_send_command_lock_"),
src/main/java/com/zy/core/thread/impl/ZyStationThread.java
@@ -9,6 +9,7 @@
import com.zy.core.network.DeviceConnectPool;
import com.zy.core.thread.StationThread;
import com.zy.core.thread.support.RecentStationArrivalTracker;
import com.zy.core.thread.support.StationErrLogSupport;
import com.alibaba.fastjson.JSON;
import com.core.common.DateUtils;
import com.core.common.SpringUtils;
@@ -160,6 +161,7 @@
        }
        OutputQueue.DEVP.offer(MessageFormat.format("【{0}】[id:{1}] <<<<< 实时数据更新成功",DateUtils.convert(new Date()), deviceConfig.getDeviceNo()));
        StationErrLogSupport.sync(deviceConfig, redisUtil, statusList);
        if (System.currentTimeMillis() - deviceDataLogTime > deviceLogCollectTime) {
            //保存数据记录
src/main/java/com/zy/core/thread/impl/ZyStationV3Thread.java
@@ -31,6 +31,7 @@
import com.zy.core.network.ZyStationConnectDriver;
import com.zy.core.network.entity.ZyStationStatusEntity;
import com.zy.core.thread.support.RecentStationArrivalTracker;
import com.zy.core.thread.support.StationErrLogSupport;
import com.zy.core.utils.DeviceLogRedisKeyBuilder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@@ -165,6 +166,7 @@
        }
        OutputQueue.DEVP.offer(MessageFormat.format("【{0}】[id:{1}] <<<<< 实时数据更新成功", DateUtils.convert(new Date()), deviceConfig.getDeviceNo()));
        StationErrLogSupport.sync(deviceConfig, redisUtil, statusList);
        if (System.currentTimeMillis() - deviceDataLogTime > deviceLogCollectTime) {
            DeviceDataLog deviceDataLog = new DeviceDataLog();
src/main/java/com/zy/core/thread/impl/ZyStationV4Thread.java
@@ -32,6 +32,7 @@
import com.zy.core.network.entity.ZyStationStatusEntity;
import com.zy.core.thread.impl.v5.StationMoveSegmentExecutor;
import com.zy.core.thread.support.RecentStationArrivalTracker;
import com.zy.core.thread.support.StationErrLogSupport;
import com.zy.core.utils.DeviceLogRedisKeyBuilder;
import com.zy.system.entity.Config;
import com.zy.system.service.ConfigService;
@@ -174,6 +175,7 @@
        }
        OutputQueue.DEVP.offer(MessageFormat.format("【{0}】[id:{1}] <<<<< 实时数据更新成功", DateUtils.convert(new Date()), deviceConfig.getDeviceNo()));
        StationErrLogSupport.sync(deviceConfig, redisUtil, statusList);
        if (System.currentTimeMillis() - deviceDataLogTime > deviceLogCollectTime) {
            DeviceDataLog deviceDataLog = new DeviceDataLog();
src/main/java/com/zy/core/thread/impl/v5/StationV5StatusReader.java
@@ -18,6 +18,7 @@
import com.zy.core.network.ZyStationConnectDriver;
import com.zy.core.network.entity.ZyStationStatusEntity;
import com.zy.core.thread.support.RecentStationArrivalTracker;
import com.zy.core.thread.support.StationErrLogSupport;
import com.zy.core.utils.DeviceLogRedisKeyBuilder;
import java.text.MessageFormat;
@@ -108,6 +109,7 @@
        OutputQueue.DEVP.offer(MessageFormat.format("【{0}】[id:{1}] <<<<< 实时数据更新成功",
                DateUtils.convert(new Date()), deviceConfig.getDeviceNo()));
        StationErrLogSupport.sync(deviceConfig, redisUtil, statusList);
        if (System.currentTimeMillis() - deviceDataLogTime > deviceLogCollectTime) {
            DeviceDataLog deviceDataLog = new DeviceDataLog();
src/main/java/com/zy/core/thread/support/StationErrLogSupport.java
New file
@@ -0,0 +1,138 @@
package com.zy.core.thread.support;
import com.alibaba.fastjson.JSONObject;
import com.core.common.Cools;
import com.core.common.SpringUtils;
import com.zy.asrs.entity.BasStationErrLog;
import com.zy.asrs.entity.DeviceConfig;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.BasStationErrLogService;
import com.zy.asrs.service.WrkMastService;
import com.zy.common.utils.RedisUtil;
import com.zy.core.enums.RedisKeyType;
import com.zy.core.model.protocol.StationProtocol;
import java.util.Date;
import java.util.List;
public final class StationErrLogSupport {
    private StationErrLogSupport() {
    }
    public static void sync(DeviceConfig deviceConfig, RedisUtil redisUtil, List<StationProtocol> statusList) {
        if (redisUtil == null || statusList == null || statusList.isEmpty()) {
            return;
        }
        try {
            BasStationErrLogService errLogService = SpringUtils.getBean(BasStationErrLogService.class);
            if (errLogService == null) {
                return;
            }
            WrkMastService wrkMastService = null;
            try {
                wrkMastService = SpringUtils.getBean(WrkMastService.class);
            } catch (Exception ignore) {
            }
            for (StationProtocol stationProtocol : statusList) {
                syncOne(deviceConfig, redisUtil, errLogService, wrkMastService, stationProtocol);
            }
        } catch (Exception ignore) {
        }
    }
    private static void syncOne(DeviceConfig deviceConfig,
                                RedisUtil redisUtil,
                                BasStationErrLogService errLogService,
                                WrkMastService wrkMastService,
                                StationProtocol stationProtocol) {
        if (stationProtocol == null || stationProtocol.getStationId() == null) {
            return;
        }
        String errFlagKey = RedisKeyType.DEVICE_ERR_ACTIVE_STATION.key + stationProtocol.getStationId();
        Object active = redisUtil.get(errFlagKey);
        Date now = new Date();
        if (hasError(stationProtocol)) {
            if (active != null) {
                return;
            }
            BasStationErrLog log = new BasStationErrLog();
            Integer wrkNo = stationProtocol.getTaskNo();
            if (wrkNo != null && wrkNo > 0) {
                log.setWrkNo(wrkNo);
            }
            log.setStartTime(now);
            log.setStationId(stationProtocol.getStationId());
            log.setStaNo(stationProtocol.getTargetStaNo());
            log.setBarcode(stationProtocol.getBarcode());
            log.setErrCode(normalizeErrCode(stationProtocol.getError()));
            log.setError(resolveError(stationProtocol));
            log.setStatus(1);
            log.setCreateTime(now);
            log.setSystemStatus(buildSystemStatus(deviceConfig, stationProtocol));
            fillWorkContext(log, wrkMastService, wrkNo);
            errLogService.save(log);
            if (log.getId() != null) {
                redisUtil.set(errFlagKey, log.getId(), 60 * 60 * 24);
            }
            return;
        }
        if (active == null) {
            return;
        }
        BasStationErrLog update = new BasStationErrLog();
        update.setId(Long.valueOf(String.valueOf(active)));
        update.setEndTime(now);
        update.setStatus(2);
        update.setUpdateTime(now);
        errLogService.updateById(update);
        redisUtil.del(errFlagKey);
    }
    private static void fillWorkContext(BasStationErrLog log, WrkMastService wrkMastService, Integer wrkNo) {
        if (wrkMastService == null || wrkNo == null || wrkNo <= 0) {
            return;
        }
        WrkMast wrkMast = wrkMastService.selectByWorkNo(wrkNo);
        if (wrkMast == null) {
            return;
        }
        log.setWrkSts(wrkMast.getWrkSts());
        log.setIoType(wrkMast.getIoType());
        log.setLocNo(wrkMast.getLocNo());
        log.setSourceStaNo(wrkMast.getSourceStaNo());
        log.setSourceLocNo(wrkMast.getSourceLocNo());
        if (log.getStaNo() == null) {
            log.setStaNo(wrkMast.getStaNo());
        }
        if (Cools.isEmpty(log.getBarcode())) {
            log.setBarcode(wrkMast.getBarcode());
        }
    }
    private static boolean hasError(StationProtocol stationProtocol) {
        return normalizeErrCode(stationProtocol.getError()) != null || !Cools.isEmpty(stationProtocol.getErrorMsg());
    }
    private static Integer normalizeErrCode(Integer errCode) {
        return errCode != null && errCode > 0 ? errCode : null;
    }
    private static String resolveError(StationProtocol stationProtocol) {
        if (!Cools.isEmpty(stationProtocol.getErrorMsg())) {
            return stationProtocol.getErrorMsg();
        }
        Integer errCode = normalizeErrCode(stationProtocol.getError());
        return errCode == null ? null : "站点报警";
    }
    private static String buildSystemStatus(DeviceConfig deviceConfig, StationProtocol stationProtocol) {
        JSONObject snapshot = new JSONObject();
        if (deviceConfig != null) {
            snapshot.put("deviceNo", deviceConfig.getDeviceNo());
        }
        snapshot.put("stationProtocol", stationProtocol);
        return snapshot.toJSONString();
    }
}
src/main/resources/i18n/en-US/legacy.properties
@@ -1028,6 +1028,7 @@
双工位堆垛机异常日志=Dual-station Crane Error Log
RGV命令日志=RGV Command Log
RGV异常日志=RGV Error Log
输送站点异常日志=Conveyor Station Error Log
输送站点命令日志=Conveyor Station Command Log
系统配置=System Configuration
新增三方接口统计=Add Third-party API Statistics
@@ -1076,6 +1077,8 @@
修改RGV命令日志=Edit RGV Command Log
新增RGV异常日志=Add RGV Error Log
修改RGV异常日志=Edit RGV Error Log
新增输送站点异常日志=Add Conveyor Station Error Log
修改输送站点异常日志=Edit Conveyor Station Error Log
新增输送站点命令日志=Add Conveyor Station Command Log
修改输送站点命令日志=Edit Conveyor Station Command Log
新增系统配置=Add System Configuration
src/main/resources/i18n/zh-CN/legacy.properties
@@ -108,6 +108,7 @@
双工位堆垛机异常日志=双工位堆垛机异常日志
RGV命令日志=RGV命令日志
RGV异常日志=RGV异常日志
输送站点异常日志=输送站点异常日志
输送站点命令日志=输送站点命令日志
系统配置=系统配置
新增三方接口统计=新增三方接口统计
@@ -156,6 +157,8 @@
修改RGV命令日志=修改RGV命令日志
新增RGV异常日志=新增RGV异常日志
修改RGV异常日志=修改RGV异常日志
新增输送站点异常日志=新增输送站点异常日志
修改输送站点异常日志=修改输送站点异常日志
新增输送站点命令日志=新增输送站点命令日志
修改输送站点命令日志=修改输送站点命令日志
新增系统配置=新增系统配置
src/main/resources/mapper/BasStationErrLogMapper.xml
New file
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zy.asrs.mapper.BasStationErrLogMapper">
    <resultMap id="BaseResultMap" type="com.zy.asrs.entity.BasStationErrLog">
        <id column="id" property="id" />
        <result column="wrk_no" property="wrkNo" />
        <result column="start_time" property="startTime" />
        <result column="end_time" property="endTime" />
        <result column="wrk_sts" property="wrkSts" />
        <result column="io_type" property="ioType" />
        <result column="station_id" property="stationId" />
        <result column="loc_no" property="locNo" />
        <result column="sta_no" property="staNo" />
        <result column="source_sta_no" property="sourceStaNo" />
        <result column="source_loc_no" property="sourceLocNo" />
        <result column="barcode" property="barcode" />
        <result column="err_code" property="errCode" />
        <result column="error" property="error" />
        <result column="status" property="status" />
        <result column="create_time" property="createTime" />
        <result column="create_by" property="createBy" />
        <result column="update_time" property="updateTime" />
        <result column="update_by" property="updateBy" />
        <result column="memo" property="memo" />
        <result column="system_status" property="systemStatus" />
    </resultMap>
</mapper>
src/main/resources/sql/20260331_add_station_err_log.sql
New file
@@ -0,0 +1,172 @@
CREATE TABLE IF NOT EXISTS `asr_bas_station_err_log` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `wrk_no` int DEFAULT NULL COMMENT '工作号',
  `start_time` datetime DEFAULT NULL COMMENT '发生时间',
  `end_time` datetime DEFAULT NULL COMMENT '结束时间',
  `wrk_sts` bigint DEFAULT NULL COMMENT '工作状态',
  `io_type` int DEFAULT NULL COMMENT '入出库类型',
  `station_id` int DEFAULT NULL COMMENT '站点号',
  `loc_no` varchar(64) DEFAULT NULL COMMENT '目标库位',
  `sta_no` int DEFAULT NULL COMMENT '目标站',
  `source_sta_no` int DEFAULT NULL COMMENT '源站',
  `source_loc_no` varchar(64) DEFAULT NULL COMMENT '源库位',
  `barcode` varchar(128) DEFAULT NULL COMMENT '条码',
  `err_code` int DEFAULT NULL COMMENT '异常码',
  `error` text COMMENT '异常描述',
  `status` int DEFAULT NULL COMMENT '异常情况 1未处理 2已修复',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `create_by` bigint DEFAULT NULL COMMENT '创建人',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `update_by` bigint DEFAULT NULL COMMENT '更新人',
  `memo` varchar(255) DEFAULT NULL COMMENT '备注',
  `system_status` longtext COMMENT '系统状态快照',
  PRIMARY KEY (`id`),
  KEY `idx_station_err_log_wrk_no` (`wrk_no`),
  KEY `idx_station_err_log_station_id` (`station_id`),
  KEY `idx_station_err_log_status` (`status`),
  KEY `idx_station_err_log_start_time` (`start_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='输送站点异常日志';
SET @wrk_analysis_table_exists := (
  SELECT COUNT(*)
  FROM `information_schema`.`TABLES`
  WHERE `TABLE_SCHEMA` = DATABASE()
    AND `TABLE_NAME` = 'asr_wrk_analysis'
);
SET @station_fault_count_exists := (
  SELECT COUNT(*)
  FROM `information_schema`.`COLUMNS`
  WHERE `TABLE_SCHEMA` = DATABASE()
    AND `TABLE_NAME` = 'asr_wrk_analysis'
    AND `COLUMN_NAME` = 'station_fault_count'
);
SET @station_fault_duration_exists := (
  SELECT COUNT(*)
  FROM `information_schema`.`COLUMNS`
  WHERE `TABLE_SCHEMA` = DATABASE()
    AND `TABLE_NAME` = 'asr_wrk_analysis'
    AND `COLUMN_NAME` = 'station_fault_duration_ms'
);
SET @sql_station_fault_count := IF(
  @wrk_analysis_table_exists = 1 AND @station_fault_count_exists = 0,
  'ALTER TABLE `asr_wrk_analysis` ADD COLUMN `station_fault_count` int DEFAULT 0 COMMENT ''输送站点故障次数'' AFTER `rgv_fault_duration_ms`',
  'SELECT 1'
);
PREPARE stmt_station_fault_count FROM @sql_station_fault_count;
EXECUTE stmt_station_fault_count;
DEALLOCATE PREPARE stmt_station_fault_count;
SET @sql_station_fault_duration := IF(
  @wrk_analysis_table_exists = 1 AND @station_fault_duration_exists = 0,
  'ALTER TABLE `asr_wrk_analysis` ADD COLUMN `station_fault_duration_ms` bigint DEFAULT 0 COMMENT ''输送站点故障耗时毫秒'' AFTER `station_fault_count`',
  'SELECT 1'
);
PREPARE stmt_station_fault_duration FROM @sql_station_fault_duration;
EXECUTE stmt_station_fault_duration;
DEALLOCATE PREPARE stmt_station_fault_duration;
UPDATE `asr_wrk_analysis` a
LEFT JOIN (
    SELECT
        e.`wrk_no`,
        COUNT(*) AS `fault_count`,
        SUM(
            CASE
                WHEN e.`start_time` IS NULL THEN 0
                ELSE GREATEST(
                    TIMESTAMPDIFF(
                        MICROSECOND,
                        e.`start_time`,
                        COALESCE(e.`end_time`, wl.`modi_time`, wl.`io_time`, wl.`appe_time`, e.`start_time`)
                    ),
                    0
                ) DIV 1000
            END
        ) AS `fault_duration_ms`
    FROM `asr_bas_station_err_log` e
    LEFT JOIN `asr_wrk_mast_log` wl ON wl.`wrk_no` = e.`wrk_no`
    GROUP BY e.`wrk_no`
) s ON s.`wrk_no` = a.`wrk_no`
SET a.`station_fault_count` = COALESCE(s.`fault_count`, 0),
    a.`station_fault_duration_ms` = COALESCE(s.`fault_duration_ms`, 0),
    a.`fault_count` = COALESCE(a.`crn_fault_count`, 0)
        + COALESCE(a.`dual_crn_fault_count`, 0)
        + COALESCE(a.`rgv_fault_count`, 0)
        + COALESCE(s.`fault_count`, 0),
    a.`fault_duration_ms` = COALESCE(a.`crn_fault_duration_ms`, 0)
        + COALESCE(a.`dual_crn_fault_duration_ms`, 0)
        + COALESCE(a.`rgv_fault_duration_ms`, 0)
        + COALESCE(s.`fault_duration_ms`, 0),
    a.`has_fault` = CASE
        WHEN COALESCE(a.`crn_fault_count`, 0)
            + COALESCE(a.`dual_crn_fault_count`, 0)
            + COALESCE(a.`rgv_fault_count`, 0)
            + COALESCE(s.`fault_count`, 0) > 0 THEN 1
        ELSE 0
    END,
    a.`update_time` = NOW()
WHERE @wrk_analysis_table_exists = 1;
SET @station_err_log_parent_id := COALESCE(
  (
    SELECT `id`
    FROM `sys_resource`
    WHERE `code` = 'logReport' AND `level` = 1
    ORDER BY `id`
    LIMIT 1
  ),
  (
    SELECT `id`
    FROM `sys_resource`
    WHERE `code` = 'develop' AND `level` = 1
    ORDER BY `id`
    LIMIT 1
  )
);
INSERT INTO `sys_resource`(`code`, `name`, `resource_id`, `level`, `sort`, `status`)
SELECT 'basStationErrLog/basStationErrLog.html', '输送站点异常日志', @station_err_log_parent_id, 2, 995, 1
FROM dual
WHERE @station_err_log_parent_id IS NOT NULL
  AND NOT EXISTS (
    SELECT 1
    FROM `sys_resource`
    WHERE `code` = 'basStationErrLog/basStationErrLog.html' AND `level` = 2
  );
UPDATE `sys_resource`
SET `name` = '输送站点异常日志',
    `resource_id` = @station_err_log_parent_id,
    `level` = 2,
    `sort` = 995,
    `status` = 1
WHERE `code` = 'basStationErrLog/basStationErrLog.html' AND `level` = 2;
SET @station_err_log_id := (
  SELECT `id`
  FROM `sys_resource`
  WHERE `code` = 'basStationErrLog/basStationErrLog.html' AND `level` = 2
  ORDER BY `id`
  LIMIT 1
);
INSERT INTO `sys_resource`(`code`, `name`, `resource_id`, `level`, `sort`, `status`)
SELECT 'basStationErrLog/basStationErrLog.html#view', '查看', @station_err_log_id, 3, 1, 1
FROM dual
WHERE @station_err_log_id IS NOT NULL
  AND NOT EXISTS (
    SELECT 1
    FROM `sys_resource`
    WHERE `code` = 'basStationErrLog/basStationErrLog.html#view' AND `level` = 3
  );
UPDATE `sys_resource`
SET `name` = '查看',
    `resource_id` = @station_err_log_id,
    `level` = 3,
    `sort` = 1,
    `status` = 1
WHERE `code` = 'basStationErrLog/basStationErrLog.html#view' AND `level` = 3;
src/main/webapp/static/js/basStationErrLog/basStationErrLog.js
New file
@@ -0,0 +1,1165 @@
(function () {
    var simpleEntityName = 'basStationErrLog';
    var entityName = 'BasStationErrLog';
    var primaryKeyField = 'id';
    var fieldMeta = dedupeFieldMeta([
    {
        field: 'id',
        columnName: 'id',
        label: '编  号',
        tableProp: 'id',
        exportField: 'id',
        kind: 'text',
        valueType: 'number',
        required: true,
        primaryKey: true,
        sortable: true,
        textarea: false,
        minWidth: 90,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: '1',
        checkboxInactiveRaw: '0'
    },
    {
        field: 'wrkNo',
        columnName: 'wrk_no',
        label: '工 作 号',
        tableProp: 'wrkNo',
        exportField: 'wrkNo',
        kind: 'text',
        valueType: 'number',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 116,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: '1',
        checkboxInactiveRaw: '0'
    },
    {
        field: 'startTime',
        columnName: 'start_time',
        label: '发生时间',
        tableProp: 'startTime$',
        exportField: 'startTime$',
        kind: 'date',
        valueType: 'string',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 168,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: 'Y',
        checkboxInactiveRaw: 'N'
    },
    {
        field: 'endTime',
        columnName: 'end_time',
        label: '结束时间',
        tableProp: 'endTime$',
        exportField: 'endTime$',
        kind: 'date',
        valueType: 'string',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 168,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: 'Y',
        checkboxInactiveRaw: 'N'
    },
    {
        field: 'wrkSts',
        columnName: 'wrk_sts',
        label: '工作状态',
        tableProp: 'wrkSts$',
        exportField: 'wrkSts$',
        kind: 'foreign',
        valueType: 'number',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 110,
        enumOptions: [],
        foreignQuery: 'basWrkStatus',
        checkboxActiveRaw: '1',
        checkboxInactiveRaw: '0'
    },
    {
        field: 'ioType',
        columnName: 'io_type',
        label: '入出库类型',
        tableProp: 'ioType$',
        exportField: 'ioType$',
        kind: 'foreign',
        valueType: 'number',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 116,
        enumOptions: [],
        foreignQuery: 'basWrkIotype',
        checkboxActiveRaw: '1',
        checkboxInactiveRaw: '0'
    },
    {
        field: 'stationId',
        columnName: 'station_id',
        label: '输送站点',
        tableProp: 'stationLabel$',
        exportField: 'stationLabel$',
        kind: 'foreign',
        valueType: 'number',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 130,
        enumOptions: [],
        foreignQuery: 'basStation',
        checkboxActiveRaw: '1',
        checkboxInactiveRaw: '0'
    },
    {
        field: 'locNo',
        columnName: 'loc_no',
        label: '目标库位',
        tableProp: 'locNo',
        exportField: 'locNo',
        kind: 'text',
        valueType: 'string',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 110,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: 'Y',
        checkboxInactiveRaw: 'N'
    },
    {
        field: 'staNo',
        columnName: 'sta_no',
        label: '目 标 站',
        tableProp: 'staNo',
        exportField: 'staNo',
        kind: 'text',
        valueType: 'number',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 116,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: '1',
        checkboxInactiveRaw: '0'
    },
    {
        field: 'sourceStaNo',
        columnName: 'source_sta_no',
        label: '源  站',
        tableProp: 'sourceStaNo',
        exportField: 'sourceStaNo',
        kind: 'text',
        valueType: 'number',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 110,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: '1',
        checkboxInactiveRaw: '0'
    },
    {
        field: 'sourceLocNo',
        columnName: 'source_loc_no',
        label: '源 库 位',
        tableProp: 'sourceLocNo',
        exportField: 'sourceLocNo',
        kind: 'text',
        valueType: 'string',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 116,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: 'Y',
        checkboxInactiveRaw: 'N'
    },
    {
        field: 'barcode',
        columnName: 'barcode',
        label: '条  码',
        tableProp: 'barcode',
        exportField: 'barcode',
        kind: 'text',
        valueType: 'string',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 110,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: 'Y',
        checkboxInactiveRaw: 'N'
    },
    {
        field: 'errCode',
        columnName: 'err_code',
        label: '异 常 码',
        tableProp: 'errCode',
        exportField: 'errCode',
        kind: 'text',
        valueType: 'number',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 116,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: '1',
        checkboxInactiveRaw: '0'
    },
    {
        field: 'error',
        columnName: 'error',
        label: '异  常',
        tableProp: 'error',
        exportField: 'error',
        kind: 'text',
        valueType: 'string',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 110,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: 'Y',
        checkboxInactiveRaw: 'N'
    },
    {
        field: 'status',
        columnName: 'status',
        label: '异常情况',
        tableProp: 'status$',
        exportField: 'status$',
        kind: 'enum',
        valueType: 'number',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 120,
        enumOptions: [{ rawValue: '1', label: '未处理' }, { rawValue: '2', label: '已修复' }],
        foreignQuery: '',
        checkboxActiveRaw: '1',
        checkboxInactiveRaw: '0'
    },
    {
        field: 'createTime',
        columnName: 'create_time',
        label: '添加时间',
        tableProp: 'createTime$',
        exportField: 'createTime$',
        kind: 'date',
        valueType: 'string',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 168,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: 'Y',
        checkboxInactiveRaw: 'N'
    },
    {
        field: 'createBy',
        columnName: 'create_by',
        label: '添加人员',
        tableProp: 'createBy$',
        exportField: 'createBy$',
        kind: 'foreign',
        valueType: 'number',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 110,
        enumOptions: [],
        foreignQuery: 'user',
        checkboxActiveRaw: '1',
        checkboxInactiveRaw: '0'
    },
    {
        field: 'updateTime',
        columnName: 'update_time',
        label: '修改时间',
        tableProp: 'updateTime$',
        exportField: 'updateTime$',
        kind: 'date',
        valueType: 'string',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 168,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: 'Y',
        checkboxInactiveRaw: 'N'
    },
    {
        field: 'updateBy',
        columnName: 'update_by',
        label: '修改人员',
        tableProp: 'updateBy$',
        exportField: 'updateBy$',
        kind: 'foreign',
        valueType: 'number',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 110,
        enumOptions: [],
        foreignQuery: 'user',
        checkboxActiveRaw: '1',
        checkboxInactiveRaw: '0'
    },
    {
        field: 'memo',
        columnName: 'memo',
        label: '备  注',
        tableProp: 'memo',
        exportField: 'memo',
        kind: 'text',
        valueType: 'string',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: true,
        minWidth: 180,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: 'Y',
        checkboxInactiveRaw: 'N'
    },
    {
        field: 'systemStatus',
        columnName: 'system_status',
        label: '系统状态数据',
        tableProp: 'systemStatus',
        exportField: 'systemStatus',
        kind: 'text',
        valueType: 'string',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 134,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: 'Y',
        checkboxInactiveRaw: 'N'
    }
    ]);
    function formatFieldLabel(field) {
        var raw = field && field.label ? String(field.label).trim() : '';
        if (raw) {
            return raw;
        }
        raw = field && field.columnName ? field.columnName : (field && field.field ? field.field : '');
        if (!raw) {
            return '';
        }
        raw = String(raw)
            .replace(/\$/g, '')
            .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
            .replace(/_/g, ' ')
            .replace(/\s+/g, ' ')
            .trim();
        return raw.replace(/\b[a-z]/g, function (letter) {
            return letter.toUpperCase();
        });
    }
    function dedupeFieldMeta(list) {
        var result = [];
        var seen = {};
        (list || []).forEach(function (field) {
            if (!field || !field.field || seen[field.field]) {
                return;
            }
            field.label = formatFieldLabel(field);
            seen[field.field] = true;
            result.push(field);
        });
        return result;
    }
    function isEmptyValue(value) {
        return value === null || value === undefined || value === '';
    }
    function stringValue(value) {
        return isEmptyValue(value) ? '' : String(value);
    }
    function valueOrDash(value) {
        return isEmptyValue(value) ? '--' : value;
    }
    function normalizeOptionValue(field, rawValue) {
        if (rawValue === null || rawValue === undefined) {
            return null;
        }
        if (rawValue === '') {
            return '';
        }
        if (field && field.valueType === 'number') {
            var numberVal = Number(rawValue);
            return isNaN(numberVal) ? rawValue : numberVal;
        }
        return String(rawValue);
    }
    function isSearchableField(field) {
        return !!field && field.kind !== 'image' && !field.textarea;
    }
    function isSortableField(field) {
        if (!field) {
            return false;
        }
        if (field.primaryKey) {
            return true;
        }
        return field.kind !== 'image' && !field.textarea && field.kind !== 'foreign';
    }
    function defaultFieldValue(field) {
        if (field.primaryKey) {
            return null;
        }
        if (field.kind === 'checkbox') {
            return normalizeOptionValue(field, field.checkboxInactiveRaw);
        }
        return '';
    }
    function defaultSearchFieldValue(field) {
        if (field.kind === 'date') {
            return [];
        }
        if (field.kind === 'enum' || field.kind === 'checkbox') {
            return null;
        }
        return '';
    }
    function createSearchDefaults() {
        var result = {
            condition: ''
        };
        fieldMeta.forEach(function (field) {
            if (!isSearchableField(field)) {
                return;
            }
            result[field.field] = defaultSearchFieldValue(field);
        });
        return result;
    }
    function createSearchDisplayDefaults() {
        var result = {};
        fieldMeta.forEach(function (field) {
            if (field.kind === 'foreign' && isSearchableField(field)) {
                result[field.field] = '';
            }
        });
        return result;
    }
    function createDefaultVisibleColumnKeys() {
        return fieldMeta.map(function (field) {
            return field.field;
        });
    }
    function createFormDefaults() {
        var result = {};
        fieldMeta.forEach(function (field) {
            result[field.field] = defaultFieldValue(field);
        });
        return result;
    }
    function createDisplayDefaults() {
        var result = {};
        fieldMeta.forEach(function (field) {
            if (field.kind === 'foreign') {
                result[field.field] = '';
            }
        });
        return result;
    }
    function createFormRules() {
        var rules = {};
        fieldMeta.forEach(function (field) {
            if (field.primaryKey || !field.required) {
                return;
            }
            rules[field.field] = [{
                required: true,
                message: (field.kind === 'date' || field.kind === 'enum' ? '请选择' : '请输入') + field.label,
                trigger: (field.kind === 'date' || field.kind === 'enum') ? 'change' : 'blur'
            }];
        });
        return rules;
    }
    function getTableValue(row, field) {
        var prop = field.tableProp || field.field;
        if (row && !isEmptyValue(row[prop])) {
            return row[prop];
        }
        return row ? row[field.field] : '';
    }
    function isCheckboxChecked(row, field) {
        var value = row ? row[field.field] : null;
        var activeValue = normalizeOptionValue(field, field.checkboxActiveRaw);
        return String(value) === String(activeValue);
    }
    function exportCell(value) {
        return stringValue(value).replace(/\t/g, ' ').replace(/\r?\n/g, ' ');
    }
    function escapeHtml(value) {
        return exportCell(value)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#39;');
    }
    function buildPayload(form) {
        var payload = {};
        fieldMeta.forEach(function (field) {
            var value = form[field.field];
            if (field.primaryKey) {
                if (!isEmptyValue(value)) {
                    payload[field.field] = value;
                }
                return;
            }
            if (field.kind === 'foreign' && isEmptyValue(value)) {
                value = null;
            }
            if (field.kind === 'enum' && value === '') {
                value = null;
            }
            if (field.kind === 'checkbox' && isEmptyValue(value)) {
                value = normalizeOptionValue(field, field.checkboxInactiveRaw);
            }
            if (field.valueType === 'number' && !isEmptyValue(value)) {
                value = Number(value);
            }
            if (field.valueType === 'number' && value === '') {
                value = null;
            }
            payload[field.field] = value;
        });
        return payload;
    }
    function fillFormFromRow(row, form, display) {
        fieldMeta.forEach(function (field) {
            if (field.primaryKey) {
                form[field.field] = row[field.field];
                return;
            }
            if (field.kind === 'date') {
                form[field.field] = row[field.tableProp] || row[field.field] || '';
                return;
            }
            if (field.kind === 'foreign') {
                form[field.field] = isEmptyValue(row[field.field]) ? '' : normalizeOptionValue(field, row[field.field]);
                if (display) {
                    display[field.field] = row[field.tableProp] || (isEmptyValue(row[field.field]) ? '' : String(row[field.field]));
                }
                return;
            }
            if (field.kind === 'enum') {
                form[field.field] = isEmptyValue(row[field.field]) ? '' : normalizeOptionValue(field, row[field.field]);
                return;
            }
            if (field.kind === 'checkbox') {
                form[field.field] = isEmptyValue(row[field.field])
                    ? normalizeOptionValue(field, field.checkboxInactiveRaw)
                    : normalizeOptionValue(field, row[field.field]);
                return;
            }
            form[field.field] = isEmptyValue(row[field.field])
                ? ''
                : (field.valueType === 'number' ? String(row[field.field]) : row[field.field]);
        });
    }
    function resolveSearchParam(field) {
        if (field.kind === 'date' && field.columnName) {
            return field.columnName;
        }
        return field.field;
    }
    function createDownloadFile(filename, titles, rows) {
        var html = [
            '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40">',
            '<head><meta charset="UTF-8"></head><body><table border="1"><thead><tr>',
            titles.map(function (title) {
                return '<th>' + escapeHtml(title) + '</th>';
            }).join(''),
            '</tr></thead><tbody>',
            (rows || []).map(function (row) {
                return '<tr>' + (row || []).map(function (value) {
                    return '<td style="mso-number-format:\\@;">' + escapeHtml(value) + '</td>';
                }).join('') + '</tr>';
            }).join(''),
            '</tbody></table></body></html>'
        ].join('');
        var blob = new Blob(['\ufeff' + html], {
            type: 'application/vnd.ms-excel;charset=utf-8;'
        });
        var anchor = document.createElement('a');
        anchor.href = URL.createObjectURL(blob);
        anchor.download = filename;
        document.body.appendChild(anchor);
        anchor.click();
        setTimeout(function () {
            URL.revokeObjectURL(anchor.href);
            document.body.removeChild(anchor);
        }, 0);
    }
    function buildTimestamp() {
        var now = new Date();
        var pad = function (num) {
            return num < 10 ? '0' + num : String(num);
        };
        return now.getFullYear()
            + pad(now.getMonth() + 1)
            + pad(now.getDate())
            + '_'
            + pad(now.getHours())
            + pad(now.getMinutes())
            + pad(now.getSeconds());
    }
    function authHeaders() {
        return {
            token: localStorage.getItem('token')
        };
    }
    function handleForbidden(res) {
        if (res && res.code === 403) {
            top.location.href = baseUrl + '/';
            return true;
        }
        return false;
    }
    var sharedMethods = {
        authHeaders: authHeaders,
        handleForbidden: handleForbidden,
        valueOrDash: valueOrDash,
        stringValue: stringValue,
        getTableValue: getTableValue,
        isCheckboxChecked: isCheckboxChecked,
        normalizeOptionValue: normalizeOptionValue,
        isSortableField: isSortableField,
        getSuggestionFetcher: function (field) {
            var self = this;
            return function (queryString, callback) {
                self.fetchForeignSuggestions(field, queryString, callback);
            };
        },
        fetchForeignSuggestions: function (field, queryString, callback) {
            if (!field.foreignQuery || !queryString) {
                callback([]);
                return;
            }
            var self = this;
            $.ajax({
                url: baseUrl + '/' + field.foreignQuery + 'Query/auth',
                method: 'GET',
                headers: self.authHeaders(),
                data: { condition: queryString },
                success: function (res) {
                    if (self.handleForbidden(res)) {
                        return;
                    }
                    if (!res || res.code !== 200 || !Array.isArray(res.data)) {
                        callback([]);
                        return;
                    }
                    callback(res.data.map(function (item) {
                        return {
                            id: item.id,
                            value: item.value
                        };
                    }));
                },
                error: function () {
                    callback([]);
                }
            });
        },
        handleForeignSelect: function (field, item) {
            this.$set(this.displayTarget, field.field, item && item.value ? item.value : '');
            this.$set(this.formTarget, field.field, item && item.id !== undefined ? normalizeOptionValue(field, item.id) : '');
        },
        handleForeignInput: function (field) {
            if (!this.displayTarget || !this.formTarget) {
                return;
            }
            if (this.displayTarget[field.field]) {
                return;
            }
            this.$set(this.formTarget, field.field, '');
        }
    };
    if (document.getElementById('app')) {
        new Vue({
            el: '#app',
            data: function () {
                return {
                    fieldMeta: fieldMeta,
                    primaryKeyField: primaryKeyField,
                    loading: false,
                    exporting: false,
                    tableData: [],
                    selection: [],
                    advancedFiltersVisible: false,
                    allColumns: fieldMeta.slice(),
                    visibleColumnKeys: createDefaultVisibleColumnKeys(),
                    searchForm: createSearchDefaults(),
                    searchDisplay: createSearchDisplayDefaults(),
                    page: {
                        curr: 1,
                        limit: 15,
                        total: 0
                    },
                    sortState: {
                        prop: '',
                        order: ''
                    },
                    dialog: {
                        visible: false,
                        mode: 'create',
                        submitting: false
                    },
                    layoutTimer: null,
                    tableResizeHandler: null,
                    dialogForm: createFormDefaults(),
                    dialogDisplay: createDisplayDefaults(),
                    dialogRules: createFormRules()
                };
            },
            computed: {
                searchableFields: function () {
                    return this.fieldMeta.filter(function (field) {
                        return isSearchableField(field);
                    });
                },
                quickSearchableFields: function () {
                    var result = [];
                    this.searchableFields.forEach(function (field) {
                        if (result.length >= 3 || field.kind === 'date') {
                            return;
                        }
                        result.push(field);
                    });
                    return result;
                },
                advancedSearchableFields: function () {
                    var quickKeys = this.quickSearchableFields.map(function (field) {
                        return field.field;
                    });
                    return this.searchableFields.filter(function (field) {
                        return quickKeys.indexOf(field.field) === -1;
                    });
                },
                hasAdvancedFilters: function () {
                    return this.advancedSearchableFields.length > 0;
                },
                visibleColumns: function () {
                    var keys = this.visibleColumnKeys;
                    return this.allColumns.filter(function (field) {
                        return keys.indexOf(field.field) !== -1;
                    });
                },
                editableFields: function () {
                    return this.fieldMeta.filter(function (field) {
                        return !field.primaryKey;
                    });
                },
                exportColumns: function () {
                    return this.visibleColumns.map(function (field) {
                        return {
                            field: field.exportField || field.tableProp || field.field,
                            label: field.label
                        };
                    });
                },
                tableHeight: function () {
                    return this.advancedFiltersVisible && this.hasAdvancedFilters
                        ? 'calc(100vh - 390px)'
                        : 'calc(100vh - 300px)';
                },
                formTarget: function () {
                    return this.dialogForm;
                },
                displayTarget: function () {
                    return this.dialogDisplay;
                }
            },
            created: function () {
                this.loadTable();
            },
            mounted: function () {
                var self = this;
                self.requestTableLayout(80);
                self.tableResizeHandler = function () {
                    self.requestTableLayout(80);
                };
                window.addEventListener('resize', self.tableResizeHandler);
            },
            beforeDestroy: function () {
                if (this.layoutTimer) {
                    clearTimeout(this.layoutTimer);
                    this.layoutTimer = null;
                }
                if (this.tableResizeHandler) {
                    window.removeEventListener('resize', this.tableResizeHandler);
                    this.tableResizeHandler = null;
                }
            },
            methods: $.extend({}, sharedMethods, {
                requestTableLayout: function (delay) {
                    var self = this;
                    if (self.layoutTimer) {
                        clearTimeout(self.layoutTimer);
                    }
                    self.$nextTick(function () {
                        self.layoutTimer = setTimeout(function () {
                            var table = self.$refs.dataTable;
                            if (table && typeof table.doLayout === 'function') {
                                table.doLayout();
                            }
                        }, delay || 40);
                    });
                },
                isColumnVisible: function (fieldName) {
                    return this.visibleColumnKeys.indexOf(fieldName) !== -1;
                },
                toggleColumn: function (fieldName, visible) {
                    if (visible) {
                        if (this.visibleColumnKeys.indexOf(fieldName) === -1) {
                            this.visibleColumnKeys.push(fieldName);
                        }
                        this.requestTableLayout(80);
                        return;
                    }
                    if (this.visibleColumnKeys.length === 1) {
                        this.$message.warning('至少保留一列');
                        return;
                    }
                    this.visibleColumnKeys = this.visibleColumnKeys.filter(function (item) {
                        return item !== fieldName;
                    });
                    this.requestTableLayout(80);
                },
                selectAllColumns: function () {
                    this.visibleColumnKeys = createDefaultVisibleColumnKeys();
                    this.requestTableLayout(80);
                },
                resetColumns: function () {
                    this.visibleColumnKeys = createDefaultVisibleColumnKeys();
                    this.requestTableLayout(80);
                },
                toggleAdvancedFilters: function () {
                    this.advancedFiltersVisible = !this.advancedFiltersVisible;
                    this.requestTableLayout(260);
                },
                handleSearchForeignSelect: function (field, item) {
                    this.$set(this.searchDisplay, field.field, item && item.value ? item.value : '');
                    this.$set(this.searchForm, field.field, item && item.id !== undefined ? normalizeOptionValue(field, item.id) : '');
                },
                handleSearchForeignInput: function (field) {
                    if (this.searchDisplay[field.field]) {
                        return;
                    }
                    this.$set(this.searchForm, field.field, '');
                },
                buildQueryParams: function () {
                    var self = this;
                    var params = {
                        curr: self.page.curr,
                        limit: self.page.limit
                    };
                    if (self.searchForm.condition) {
                        params.condition = self.searchForm.condition;
                    }
                    self.searchableFields.forEach(function (field) {
                        var value = self.searchForm[field.field];
                        if (field.kind === 'date') {
                            if (value && value.length === 2) {
                                params[resolveSearchParam(field)] = value[0] + ' - ' + value[1];
                            }
                            return;
                        }
                        if (!isEmptyValue(value)) {
                            params[resolveSearchParam(field)] = value;
                        }
                    });
                    if (self.sortState.prop && self.sortState.order) {
                        params.orderByField = self.sortState.prop;
                        params.orderByType = self.sortState.order === 'ascending' ? 'asc' : 'desc';
                    }
                    return params;
                },
                loadTable: function () {
                    var self = this;
                    self.loading = true;
                    $.ajax({
                        url: baseUrl + '/' + simpleEntityName + '/list/auth',
                        method: 'GET',
                        headers: self.authHeaders(),
                        data: self.buildQueryParams(),
                        success: function (res) {
                            self.loading = false;
                            if (self.handleForbidden(res)) {
                                return;
                            }
                            if (!res || res.code !== 200) {
                                self.$message.error((res && res.msg) ? res.msg : '加载失败');
                                return;
                            }
                            var payload = res.data || {};
                            self.tableData = Array.isArray(payload.records) ? payload.records : [];
                            self.page.total = payload.total || 0;
                            self.requestTableLayout(80);
                        },
                        error: function () {
                            self.loading = false;
                            self.requestTableLayout(80);
                            self.$message.error('加载失败');
                        }
                    });
                },
                handleSearch: function () {
                    this.page.curr = 1;
                    this.loadTable();
                },
                handleReset: function () {
                    this.searchForm = createSearchDefaults();
                    this.searchDisplay = createSearchDisplayDefaults();
                    this.advancedFiltersVisible = false;
                    this.page.curr = 1;
                    this.sortState = {
                        prop: '',
                        order: ''
                    };
                    this.loadTable();
                },
                handleSelectionChange: function (rows) {
                    this.selection = rows || [];
                },
                handleSortChange: function (payload) {
                    this.sortState = {
                        prop: payload && payload.prop ? payload.prop : '',
                        order: payload && payload.order ? payload.order : ''
                    };
                    this.page.curr = 1;
                    this.loadTable();
                },
                handleCurrentChange: function (curr) {
                    this.page.curr = curr;
                    this.loadTable();
                },
                handleSizeChange: function (limit) {
                    this.page.limit = limit;
                    this.page.curr = 1;
                    this.loadTable();
                },
                resetDialogState: function () {
                    this.dialogForm = createFormDefaults();
                    this.dialogDisplay = createDisplayDefaults();
                    if (this.$refs.dialogForm) {
                        this.$refs.dialogForm.clearValidate();
                    }
                },
                openCreateDialog: function () {
                    this.dialog.mode = 'create';
                    this.dialog.visible = true;
                    this.$nextTick(this.resetDialogState);
                },
                openEditDialog: function (row) {
                    var self = this;
                    self.dialog.mode = 'edit';
                    self.dialog.visible = true;
                    self.$nextTick(function () {
                        self.resetDialogState();
                        fillFormFromRow(row, self.dialogForm, self.dialogDisplay);
                        if (self.$refs.dialogForm) {
                            self.$refs.dialogForm.clearValidate();
                        }
                    });
                },
                submitDialog: function () {
                    var self = this;
                    if (!self.$refs.dialogForm) {
                        return;
                    }
                    self.$refs.dialogForm.validate(function (valid) {
                        if (!valid) {
                            return false;
                        }
                        self.dialog.submitting = true;
                        $.ajax({
                            url: baseUrl + '/' + simpleEntityName + '/' + (self.dialog.mode === 'create' ? 'add' : 'update') + '/auth',
                            method: 'POST',
                            headers: self.authHeaders(),
                            data: buildPayload(self.dialogForm),
                            success: function (res) {
                                self.dialog.submitting = false;
                                if (self.handleForbidden(res)) {
                                    return;
                                }
                                if (!res || res.code !== 200) {
                                    self.$message.error((res && res.msg) ? res.msg : '保存失败');
                                    return;
                                }
                                self.$message.success(res.msg || '保存成功');
                                self.dialog.visible = false;
                                self.loadTable();
                            },
                            error: function () {
                                self.dialog.submitting = false;
                                self.$message.error('保存失败');
                            }
                        });
                        return true;
                    });
                },
                removeSelection: function () {
                    var self = this;
                    var ids = self.selection.map(function (row) {
                        return row[self.primaryKeyField];
                    });
                    self.removeRows(ids);
                },
                removeRows: function (ids) {
                    var self = this;
                    if (!ids || ids.length === 0) {
                        self.$message.warning('请选择要删除的数据');
                        return;
                    }
                    self.$confirm('确定删除选中的记录吗?', '提示', { type: 'warning' }).then(function () {
                        $.ajax({
                            url: baseUrl + '/' + simpleEntityName + '/delete/auth',
                            method: 'POST',
                            headers: self.authHeaders(),
                            traditional: true,
                            data: { 'ids[]': ids },
                            success: function (res) {
                                if (self.handleForbidden(res)) {
                                    return;
                                }
                                if (!res || res.code !== 200) {
                                    self.$message.error((res && res.msg) ? res.msg : '删除失败');
                                    return;
                                }
                                self.$message.success(res.msg || '删除成功');
                                self.selection = [];
                                if (self.tableData.length === ids.length && self.page.curr > 1) {
                                    self.page.curr = self.page.curr - 1;
                                }
                                self.loadTable();
                            },
                            error: function () {
                                self.$message.error('删除失败');
                            }
                        });
                    }).catch(function () {});
                },
                exportRows: function () {
                    var self = this;
                    self.exporting = true;
                    var requestBody = {
                        fields: self.exportColumns.map(function (item) {
                            return item.field;
                        })
                    };
                    requestBody[simpleEntityName] = self.buildQueryParams();
                    $.ajax({
                        url: baseUrl + '/' + simpleEntityName + '/export/auth',
                        method: 'POST',
                        headers: $.extend({ 'Content-Type': 'application/json;charset=UTF-8' }, self.authHeaders()),
                        data: JSON.stringify(requestBody),
                        success: function (res) {
                            self.exporting = false;
                            if (self.handleForbidden(res)) {
                                return;
                            }
                            if (!res || res.code !== 200) {
                                self.$message.error((res && res.msg) ? res.msg : '导出失败');
                                return;
                            }
                            createDownloadFile(
                                simpleEntityName + '_' + buildTimestamp() + '.xls',
                                self.exportColumns.map(function (item) {
                                    return item.label;
                                }),
                                Array.isArray(res.data) ? res.data : []
                            );
                            self.$message.success('导出成功');
                        },
                        error: function () {
                            self.exporting = false;
                            self.$message.error('导出失败');
                        }
                    });
                }
            })
        });
    }
})();
src/main/webapp/static/js/wrkMastLog/wrkMastLog.js
@@ -1345,20 +1345,20 @@
        },
        wrkSts: {
            label: '工作状态',
            kind: 'foreign',
            kind: 'enum',
            tableProp: 'wrkSts$',
            exportField: 'wrkSts$',
            foreignQuery: 'basWrkStatus',
            enumOptions: [],
            searchable: true,
            quickSearch: true,
            minWidth: 160
        },
        ioType: {
            label: '入出库类型',
            kind: 'foreign',
            kind: 'enum',
            tableProp: 'ioType$',
            exportField: 'ioType$',
            foreignQuery: 'basWrkIotype',
            enumOptions: [],
            searchable: true,
            quickSearch: true,
            minWidth: 160
@@ -1385,10 +1385,18 @@
            minWidth: 90
        },
        sourceStaNo: {
            label: '源站'
            label: '源站',
            kind: 'enum',
            enumOptions: [],
            searchable: true,
            minWidth: 120
        },
        staNo: {
            label: '目标站'
            label: '目标站',
            kind: 'enum',
            enumOptions: [],
            searchable: true,
            minWidth: 120
        },
        sourceLocNo: {
            label: '源库位',
@@ -1416,6 +1424,7 @@
        },
        barcode: {
            label: '条码',
            searchable: true,
            minWidth: 140
        },
        modiUser: {
@@ -1804,6 +1813,39 @@
        };
    }
    function getFieldByName(fieldName) {
        for (var i = 0; i < fieldMeta.length; i += 1) {
            if (fieldMeta[i] && fieldMeta[i].field === fieldName) {
                return fieldMeta[i];
            }
        }
        return null;
    }
    function buildEnumOptions(records, valueField, labelField) {
        var options = [];
        var seen = {};
        (records || []).forEach(function (item) {
            if (!item) {
                return;
            }
            var rawValue = item[valueField];
            if (isEmptyValue(rawValue)) {
                return;
            }
            var key = String(rawValue);
            if (seen[key]) {
                return;
            }
            seen[key] = true;
            options.push({
                rawValue: key,
                label: isEmptyValue(item[labelField]) ? key : String(item[labelField])
            });
        });
        return options;
    }
    function handleForbidden(res) {
        if (res && res.code === 403) {
            top.location.href = baseUrl + '/';
@@ -1961,6 +2003,7 @@
                }
            },
            created: function () {
                this.loadSearchEnumOptions();
                this.loadTable();
            },
            mounted: function () {
@@ -2038,6 +2081,63 @@
                    }
                    this.$set(this.searchForm, field.field, '');
                },
                loadSearchEnumOptions: function () {
                    var self = this;
                    [
                        {
                            field: 'wrkSts',
                            url: baseUrl + '/basWrkStatus/list/auth',
                            valueField: 'wrkSts',
                            labelField: 'wrkDesc',
                            limit: 200
                        },
                        {
                            field: 'ioType',
                            url: baseUrl + '/basWrkIotype/list/auth',
                            valueField: 'ioType',
                            labelField: 'ioDesc',
                            limit: 200
                        },
                        {
                            field: 'staNo',
                            url: baseUrl + '/basStation/list/auth',
                            valueField: 'stationId',
                            labelField: 'stationId',
                            limit: 500
                        },
                        {
                            field: 'sourceStaNo',
                            url: baseUrl + '/basStation/list/auth',
                            valueField: 'stationId',
                            labelField: 'stationId',
                            limit: 500
                        }
                    ].forEach(function (config) {
                        $.ajax({
                            url: config.url,
                            method: 'GET',
                            headers: self.authHeaders(),
                            data: {
                                curr: 1,
                                limit: config.limit
                            },
                            success: function (res) {
                                if (self.handleForbidden(res)) {
                                    return;
                                }
                                if (!res || res.code !== 200) {
                                    return;
                                }
                                var payload = res.data || {};
                                var field = getFieldByName(config.field);
                                if (!field) {
                                    return;
                                }
                                self.$set(field, 'enumOptions', buildEnumOptions(payload.records, config.valueField, config.labelField));
                            }
                        });
                    });
                },
                buildQueryParams: function () {
                    var self = this;
                    var params = {
src/main/webapp/views/basStationErrLog/basStationErrLog.html
New file
@@ -0,0 +1,670 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <title>BasStationErrLog 管理</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="../../static/vue/element/element.css">
    <link rel="stylesheet" href="../../static/css/cool.css">
    <style>
        :root {
            --card-bg: rgba(255, 255, 255, 0.94);
            --card-border: rgba(216, 226, 238, 0.95);
            --text-main: #243447;
        }
        [v-cloak] {
            display: none;
        }
        html,
        body {
            margin: 0;
            min-height: 100%;
            color: var(--text-main);
            font-family: "Avenir Next", "PingFang SC", "Microsoft YaHei", sans-serif;
            background:
                radial-gradient(1000px 420px at 0% -10%, rgba(44, 107, 193, 0.12), transparent 56%),
                radial-gradient(900px 400px at 100% 0%, rgba(28, 150, 126, 0.10), transparent 58%),
                linear-gradient(180deg, #f2f6fb 0%, #f8fafc 100%);
        }
        .page-shell {
            max-width: 1700px;
            margin: 0 auto;
            padding: 14px;
            box-sizing: border-box;
        }
        .card-shell {
            position: relative;
            border-radius: 24px;
            border: 1px solid var(--card-border);
            background:
                radial-gradient(760px 220px at -8% 0%, rgba(43, 117, 196, 0.05), transparent 55%),
                radial-gradient(680px 200px at 108% 10%, rgba(24, 150, 129, 0.05), transparent 58%),
                var(--card-bg);
            box-shadow: 0 16px 32px rgba(44, 67, 96, 0.08);
            overflow: hidden;
        }
        .card-body {
            position: relative;
            z-index: 1;
        }
        .list-toolbar {
            padding: 12px 16px 10px;
            border-bottom: 1px solid rgba(222, 230, 239, 0.92);
        }
        .toolbar-main {
            display: flex;
            align-items: flex-start;
            justify-content: space-between;
            gap: 8px;
            flex-wrap: wrap;
        }
        .toolbar-left {
            flex: 1 1 960px;
            display: flex;
            align-items: center;
            gap: 8px;
            flex-wrap: wrap;
        }
        .toolbar-search {
            flex: 1 1 auto;
            display: flex;
            align-items: center;
            gap: 8px;
            flex-wrap: wrap;
        }
        .toolbar-search-item {
            flex: 0 0 152px;
            min-width: 152px;
        }
        .toolbar-search-item.keyword {
            flex: 0 0 220px;
            min-width: 220px;
        }
        .toolbar-query-actions,
        .toolbar-ops {
            display: flex;
            gap: 8px;
            flex-wrap: wrap;
        }
        .toolbar-ops {
            justify-content: flex-end;
        }
        .list-toolbar .el-input__inner,
        .list-toolbar .el-range-editor.el-input__inner,
        .advanced-panel .el-input__inner,
        .advanced-panel .el-range-editor.el-input__inner {
            height: 32px;
            line-height: 32px;
        }
        .list-toolbar .el-range-editor.el-input__inner,
        .advanced-panel .el-range-editor.el-input__inner {
            align-items: center;
        }
        .list-toolbar .el-input__icon,
        .advanced-panel .el-input__icon {
            line-height: 32px;
        }
        .list-toolbar .el-range-editor .el-range__icon,
        .list-toolbar .el-range-editor .el-range-separator,
        .list-toolbar .el-range-editor .el-range__close-icon,
        .advanced-panel .el-range-editor .el-range__icon,
        .advanced-panel .el-range-editor .el-range-separator,
        .advanced-panel .el-range-editor .el-range__close-icon {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            height: 100%;
            line-height: 1;
        }
        .list-toolbar .el-button,
        .advanced-panel .el-button {
            padding: 8px 12px;
            border-radius: 8px;
        }
        .advanced-panel {
            padding: 10px 16px 12px;
            border-bottom: 1px solid rgba(222, 230, 239, 0.92);
            background: rgba(248, 251, 254, 0.78);
        }
        .advanced-grid {
            display: grid;
            grid-template-columns: repeat(6, minmax(0, 1fr));
            gap: 8px;
        }
        .advanced-item {
            min-width: 0;
        }
        .advanced-item.span-2 {
            grid-column: span 2;
        }
        .table-wrap {
            padding: 10px 16px;
        }
        .table-shell {
            border-radius: 20px;
            overflow: hidden;
            border: 1px solid rgba(217, 227, 238, 0.98);
            background: rgba(255, 255, 255, 0.95);
        }
        .table-shell .el-table {
            border-radius: 20px;
            overflow: hidden;
        }
        .table-shell .el-table th {
            background: #f7fafc;
            color: #53677d;
            font-weight: 700;
        }
        .payload-cell {
            display: inline-block;
            max-width: 280px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .mono {
            font-family: Menlo, Monaco, Consolas, "Liberation Mono", monospace;
        }
        .pager-bar {
            padding: 0 16px 16px;
            display: flex;
            align-items: center;
            justify-content: flex-end;
        }
        .column-popover {
            max-width: 320px;
        }
        .column-popover-head {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 12px;
            margin-bottom: 10px;
            font-size: 13px;
            font-weight: 700;
            color: var(--text-main);
        }
        .column-list {
            display: grid;
            grid-template-columns: repeat(2, minmax(0, 1fr));
            gap: 8px 10px;
            max-height: 280px;
            overflow: auto;
            padding-right: 4px;
        }
        .dialog-panel .el-dialog {
            border-radius: 24px;
            overflow: hidden;
        }
        .dialog-panel .el-dialog__header {
            padding: 22px 24px 12px;
            background: linear-gradient(180deg, #f8fbff 0%, #f3f7fb 100%);
            border-bottom: 1px solid rgba(224, 232, 241, 0.92);
        }
        .dialog-panel .el-dialog__title {
            font-weight: 700;
            color: var(--text-main);
        }
        .dialog-panel .el-dialog__body {
            padding: 18px 24px 8px;
        }
        .dialog-footer {
            display: flex;
            justify-content: flex-end;
            gap: 10px;
        }
        @media (max-width: 1520px) {
            .advanced-grid {
                grid-template-columns: repeat(5, minmax(0, 1fr));
            }
        }
        @media (max-width: 1280px) {
            .advanced-grid {
                grid-template-columns: repeat(4, minmax(0, 1fr));
            }
        }
        @media (max-width: 960px) {
            .toolbar-left {
                flex-basis: 100%;
            }
            .advanced-grid {
                grid-template-columns: repeat(3, minmax(0, 1fr));
            }
            .advanced-item.span-2 {
                grid-column: span 2;
            }
        }
        @media (max-width: 720px) {
            .page-shell {
                padding: 12px;
            }
            .toolbar-search-item,
            .toolbar-search-item.keyword {
                min-width: 100%;
                flex-basis: 100%;
            }
            .toolbar-query-actions,
            .toolbar-ops {
                width: 100%;
            }
            .advanced-grid {
                grid-template-columns: repeat(2, minmax(0, 1fr));
            }
            .advanced-item.span-2 {
                grid-column: span 2;
            }
        }
        @media (max-width: 560px) {
            .advanced-grid {
                grid-template-columns: 1fr;
            }
            .advanced-item.span-2 {
                grid-column: auto;
            }
            .list-toolbar,
            .advanced-panel,
            .table-wrap,
            .pager-bar {
                padding-left: 14px;
                padding-right: 14px;
            }
            .column-list {
                grid-template-columns: 1fr;
            }
        }
    </style>
</head>
<body>
<div id="app" class="page-shell" v-cloak>
    <section class="card-shell list-card">
        <div class="card-body">
            <div class="list-toolbar">
                <div class="toolbar-main">
                    <div class="toolbar-left">
                        <div class="toolbar-search">
                            <div class="toolbar-search-item keyword">
                                <el-input
                                    v-model.trim="searchForm.condition"
                                    size="small"
                                    clearable
                                    placeholder="请输入"
                                    @keyup.enter.native="handleSearch">
                                </el-input>
                            </div>
                            <div
                                v-for="field in quickSearchableFields"
                                :key="'quick-' + field.field"
                                class="toolbar-search-item">
                                <el-select
                                    v-if="field.kind === 'enum'"
                                    v-model="searchForm[field.field]"
                                    size="small"
                                    clearable
                                    :placeholder="field.label"
                                    style="width: 100%;">
                                    <el-option
                                        v-for="option in field.enumOptions"
                                        :key="'quick-' + field.field + '-' + option.rawValue"
                                        :label="option.label"
                                        :value="normalizeOptionValue(field, option.rawValue)">
                                    </el-option>
                                </el-select>
                                <el-autocomplete
                                    v-else-if="field.kind === 'foreign'"
                                    v-model="searchDisplay[field.field]"
                                    size="small"
                                    :fetch-suggestions="getSuggestionFetcher(field)"
                                    :placeholder="field.label"
                                    style="width: 100%;"
                                    @select="handleSearchForeignSelect(field, $event)"
                                    @input="handleSearchForeignInput(field)">
                                    <template slot-scope="{ item }">
                                        <div class="mono">{{ item.value }}</div>
                                        <div style="font-size:12px;color:#8a98ac;">ID: {{ item.id }}</div>
                                    </template>
                                </el-autocomplete>
                                <el-select
                                    v-else-if="field.kind === 'checkbox'"
                                    v-model="searchForm[field.field]"
                                    size="small"
                                    clearable
                                    :placeholder="field.label"
                                    style="width: 100%;">
                                    <el-option label="是" :value="normalizeOptionValue(field, field.checkboxActiveRaw)"></el-option>
                                    <el-option label="否" :value="normalizeOptionValue(field, field.checkboxInactiveRaw)"></el-option>
                                </el-select>
                                <el-input
                                    v-else
                                    v-model.trim="searchForm[field.field]"
                                    size="small"
                                    clearable
                                    :placeholder="field.label"
                                    @keyup.enter.native="handleSearch">
                                </el-input>
                            </div>
                        </div>
                        <div class="toolbar-query-actions">
                            <el-button size="small" type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
                            <el-button size="small" icon="el-icon-refresh-left" @click="handleReset">重置</el-button>
                            <el-button
                                v-if="hasAdvancedFilters"
                                size="small"
                                plain
                                :icon="advancedFiltersVisible ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"
                                @click="toggleAdvancedFilters">
                                {{ advancedFiltersVisible ? '收起' : '筛选' }}
                            </el-button>
                        </div>
                    </div>
                    <div class="toolbar-ops">
                        <el-button size="small" type="primary" plain icon="el-icon-plus" @click="openCreateDialog">新增</el-button>
                        <el-button size="small" type="danger" plain icon="el-icon-delete" :disabled="selection.length === 0" @click="removeSelection">删除</el-button>
                        <el-popover
                            placement="bottom"
                            width="320"
                            trigger="click"
                            popper-class="column-popover">
                            <div class="column-popover-head">
                                <span>列设置</span>
                                <div>
                                    <el-button type="text" @click="selectAllColumns">全选</el-button>
                                    <el-button type="text" @click="resetColumns">重置</el-button>
                                </div>
                            </div>
                            <div class="column-list">
                                <el-checkbox
                                    v-for="field in allColumns"
                                    :key="'column-' + field.field"
                                    :value="isColumnVisible(field.field)"
                                    @change="toggleColumn(field.field, $event)">
                                    {{ field.label }}
                                </el-checkbox>
                            </div>
                            <el-button slot="reference" size="small" plain icon="el-icon-setting">列设置</el-button>
                        </el-popover>
                        <el-button size="small" plain icon="el-icon-download" :loading="exporting" @click="exportRows">导出</el-button>
                    </div>
                </div>
            </div>
            <el-collapse-transition>
                <div v-show="advancedFiltersVisible && hasAdvancedFilters" class="advanced-panel">
                    <div class="advanced-grid">
                        <div
                            v-for="field in advancedSearchableFields"
                            :key="'advanced-' + field.field"
                            :class="['advanced-item', field.kind === 'date' ? 'span-2' : '']">
                            <el-date-picker
                                v-if="field.kind === 'date'"
                                v-model="searchForm[field.field]"
                                size="small"
                                type="datetimerange"
                                unlink-panels
                                range-separator="至"
                                :start-placeholder="field.label + '开始'"
                                :end-placeholder="field.label + '结束'"
                                value-format="yyyy-MM-dd HH:mm:ss"
                                style="width: 100%;">
                            </el-date-picker>
                            <el-select
                                v-else-if="field.kind === 'enum'"
                                v-model="searchForm[field.field]"
                                size="small"
                                clearable
                                :placeholder="field.label"
                                style="width: 100%;">
                                <el-option
                                    v-for="option in field.enumOptions"
                                    :key="'advanced-' + field.field + '-' + option.rawValue"
                                    :label="option.label"
                                    :value="normalizeOptionValue(field, option.rawValue)">
                                </el-option>
                            </el-select>
                            <el-autocomplete
                                v-else-if="field.kind === 'foreign'"
                                v-model="searchDisplay[field.field]"
                                size="small"
                                :fetch-suggestions="getSuggestionFetcher(field)"
                                :placeholder="field.label"
                                style="width: 100%;"
                                @select="handleSearchForeignSelect(field, $event)"
                                @input="handleSearchForeignInput(field)">
                                <template slot-scope="{ item }">
                                    <div class="mono">{{ item.value }}</div>
                                    <div style="font-size:12px;color:#8a98ac;">ID: {{ item.id }}</div>
                                </template>
                            </el-autocomplete>
                            <el-select
                                v-else-if="field.kind === 'checkbox'"
                                v-model="searchForm[field.field]"
                                size="small"
                                clearable
                                :placeholder="field.label"
                                style="width: 100%;">
                                <el-option label="是" :value="normalizeOptionValue(field, field.checkboxActiveRaw)"></el-option>
                                <el-option label="否" :value="normalizeOptionValue(field, field.checkboxInactiveRaw)"></el-option>
                            </el-select>
                            <el-input
                                v-else
                                v-model.trim="searchForm[field.field]"
                                size="small"
                                clearable
                                :placeholder="field.label"
                                @keyup.enter.native="handleSearch">
                            </el-input>
                        </div>
                    </div>
                </div>
            </el-collapse-transition>
            <div class="table-wrap">
                <div class="table-shell">
                    <el-table
                        ref="dataTable"
                        :key="'table-' + visibleColumnKeys.join('|')"
                        v-loading="loading"
                        :data="tableData"
                        border
                        stripe
                        :height="tableHeight"
                        @selection-change="handleSelectionChange"
                        @sort-change="handleSortChange">
                        <el-table-column type="selection" width="52" align="center"></el-table-column>
                        <el-table-column
                            v-for="field in visibleColumns"
                            :key="field.field"
                            :prop="field.field"
                            :label="field.label"
                            :width="field.primaryKey ? 90 : null"
                            :min-width="field.primaryKey ? null : field.minWidth"
                            :sortable="isSortableField(field) ? 'custom' : false"
                            :show-overflow-tooltip="field.kind !== 'image'"
                            align="center">
                            <template slot-scope="scope">
                                <el-image
                                    v-if="field.kind === 'image' && getTableValue(scope.row, field)"
                                    :src="getTableValue(scope.row, field)"
                                    fit="cover"
                                    style="width: 48px; height: 48px; border-radius: 10px;">
                                </el-image>
                                <el-tag v-else-if="field.kind === 'enum'" size="mini" type="success">
                                    {{ valueOrDash(getTableValue(scope.row, field)) }}
                                </el-tag>
                                <el-tag v-else-if="field.kind === 'checkbox'" size="mini" :type="isCheckboxChecked(scope.row, field) ? 'success' : 'info'">
                                    {{ isCheckboxChecked(scope.row, field) ? '是' : '否' }}
                                </el-tag>
                                <span v-else-if="field.textarea" class="payload-cell mono" :title="stringValue(getTableValue(scope.row, field))">
                                    {{ valueOrDash(getTableValue(scope.row, field)) }}
                                </span>
                                <span v-else>{{ valueOrDash(getTableValue(scope.row, field)) }}</span>
                            </template>
                        </el-table-column>
                        <el-table-column label="操作" width="160" fixed="right" align="center">
                            <template slot-scope="scope">
                                <el-button type="text" @click="openEditDialog(scope.row)">修改</el-button>
                                <el-button type="text" style="color:#f56c6c;" @click="removeRows([scope.row[primaryKeyField]])">删除</el-button>
                            </template>
                        </el-table-column>
                    </el-table>
                </div>
            </div>
            <div class="pager-bar">
                <el-pagination
                    small
                    background
                    layout="total, sizes, prev, pager, next, jumper"
                    :current-page="page.curr"
                    :page-size="page.limit"
                    :page-sizes="[15, 30, 50, 100, 200, 500]"
                    :total="page.total"
                    @current-change="handleCurrentChange"
                    @size-change="handleSizeChange">
                </el-pagination>
            </div>
        </div>
    </section>
    <el-dialog
        class="dialog-panel"
        :title="dialog.mode === 'create' ? '新增输送站点异常日志' : '修改输送站点异常日志'"
        :visible.sync="dialog.visible"
        width="760px"
        :close-on-click-modal="false">
        <el-form
            ref="dialogForm"
            :model="dialogForm"
            :rules="dialogRules"
            label-width="110px"
            size="small">
            <el-row :gutter="16">
                <el-col
                    v-for="field in editableFields"
                    :key="'dialog-' + field.field"
                    :span="field.textarea || field.kind === 'image' ? 24 : 12">
                    <el-form-item :label="field.label" :prop="field.field">
                        <el-date-picker
                            v-if="field.kind === 'date'"
                            v-model="dialogForm[field.field]"
                            type="datetime"
                            value-format="yyyy-MM-dd HH:mm:ss"
                            :placeholder="'请选择' + field.label"
                            style="width: 100%;">
                        </el-date-picker>
                        <el-select
                            v-else-if="field.kind === 'enum'"
                            v-model="dialogForm[field.field]"
                            clearable
                            :placeholder="'请选择' + field.label"
                            style="width: 100%;">
                            <el-option
                                v-for="option in field.enumOptions"
                                :key="'dialog-' + field.field + '-' + option.rawValue"
                                :label="option.label"
                                :value="normalizeOptionValue(field, option.rawValue)">
                            </el-option>
                        </el-select>
                        <el-autocomplete
                            v-else-if="field.kind === 'foreign'"
                            v-model="dialogDisplay[field.field]"
                            :fetch-suggestions="getSuggestionFetcher(field)"
                            :placeholder="'请输入' + field.label"
                            style="width: 100%;"
                            @select="handleForeignSelect(field, $event)"
                            @input="handleForeignInput(field)">
                            <template slot-scope="{ item }">
                                <div class="mono">{{ item.value }}</div>
                                <div style="font-size:12px;color:#8a98ac;">ID: {{ item.id }}</div>
                            </template>
                        </el-autocomplete>
                        <el-switch
                            v-else-if="field.kind === 'checkbox'"
                            v-model="dialogForm[field.field]"
                            :active-value="normalizeOptionValue(field, field.checkboxActiveRaw)"
                            :inactive-value="normalizeOptionValue(field, field.checkboxInactiveRaw)"
                            active-color="#13ce66"
                            inactive-color="#c0c4cc">
                        </el-switch>
                        <el-input
                            v-else-if="field.textarea"
                            v-model.trim="dialogForm[field.field]"
                            type="textarea"
                            :rows="3"
                            :placeholder="'请输入' + field.label">
                        </el-input>
                        <el-input
                            v-else
                            v-model.trim="dialogForm[field.field]"
                            :placeholder="'请输入' + field.label">
                        </el-input>
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
        <div slot="footer" class="dialog-footer">
            <el-button @click="dialog.visible = false">取消</el-button>
            <el-button type="primary" :loading="dialog.submitting" @click="submitDialog">保存</el-button>
        </div>
    </el-dialog>
</div>
<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
<script type="text/javascript" src="../../static/vue/element/element.js"></script>
<script type="text/javascript" src="../../static/js/basStationErrLog/basStationErrLog.js?v=20260331" charset="utf-8"></script>
</body>
</html>