| | |
| | | <!DOCTYPE html> |
| | | <html lang="en"> |
| | | <html lang="zh-CN"> |
| | | |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <title>调试参数</title> |
| | | <link rel="stylesheet" href="../../static/vue/element/element.css"> |
| | | <script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script> |
| | | <script type="text/javascript" src="../../static/js/common.js?v=20260309_i18n_fix1"></script> |
| | | <script type="text/javascript" src="../../static/vue/js/vue.min.js"></script> |
| | | <script type="text/javascript" src="../../static/vue/element/element.js"></script> |
| | | <style> |
| | | .show-box { |
| | | width: 20%; |
| | | display: flex; |
| | | justify-content: flex-start; |
| | | align-items: center; |
| | | margin-bottom: 30px; |
| | | } |
| | | </style> |
| | | </head> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <title>调试参数</title> |
| | | <link rel="stylesheet" href="../../static/vue/element/element.css"> |
| | | <script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script> |
| | | <script type="text/javascript" src="../../static/js/common.js"></script> |
| | | <script type="text/javascript" src="../../static/vue/js/vue.min.js"></script> |
| | | <script type="text/javascript" src="../../static/vue/element/element.js"></script> |
| | | <style> |
| | | body { |
| | | margin: 0; |
| | | padding: 16px; |
| | | background: #f5f7fa; |
| | | color: #303133; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | <body> |
| | | <div id="app"> |
| | | <div> |
| | | <el-card class="box-card"> |
| | | <div slot="header" class="clearfix"> |
| | | <span>调度参数</span> |
| | | </div> |
| | | * { |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | <div style="display: flex;flex-wrap: wrap;"> |
| | | <div class="show-box"> |
| | | <div>调度小车同层最大数量</div> |
| | | <el-input v-model="codeMap.dispatchShuttleMaxNum" style="width: 60%;"></el-input> |
| | | </div> |
| | | .page-shell { |
| | | max-width: 1600px; |
| | | margin: 0 auto; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>入库预留小车</div> |
| | | <el-input v-model="codeMap.shuttleWrkInObligateCount" style="width: 60%;"></el-input> |
| | | </div> |
| | | .hero-card, |
| | | .filter-card, |
| | | .group-card { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>避障内圈半径</div> |
| | | <el-input v-model="codeMap.avoidInnerCircle" style="width: 60%;"></el-input> |
| | | </div> |
| | | .hero-header, |
| | | .group-header, |
| | | .filter-bar { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | justify-content: space-between; |
| | | gap: 16px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>避障外圈半径</div> |
| | | <el-input v-model="codeMap.avoidOuterCircle" style="width: 60%;"></el-input> |
| | | </div> |
| | | .hero-title { |
| | | font-size: 20px; |
| | | font-weight: 600; |
| | | line-height: 1.2; |
| | | margin: 0 0 4px; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>地图母轨方向(x,y)</div> |
| | | <el-input v-model="codeMap.direction_map" style="width: 60%;"></el-input> |
| | | </div> |
| | | .hero-desc, |
| | | .group-desc { |
| | | margin: 0; |
| | | color: #606266; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>小车(x,y)命令运行方向颠倒</div> |
| | | <el-radio border v-model="codeMap.shuttleDirectionReverse" label="Y">开</el-radio> |
| | | <el-radio border v-model="codeMap.shuttleDirectionReverse" label="N">关</el-radio> |
| | | </div> |
| | | .hero-actions, |
| | | .group-actions, |
| | | .row-actions, |
| | | .group-tags { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>小车出提升机近点距离</div> |
| | | <el-input v-model="codeMap.shuttleOutLiftLocationDistance" style="width: 60%;"></el-input> |
| | | </div> |
| | | .summary-strip { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>小车移动连续下发指令</div> |
| | | <el-radio border v-model="codeMap.shuttleMoveCommandsContinuously" label="Y">开</el-radio> |
| | | <el-radio border v-model="codeMap.shuttleMoveCommandsContinuously" label="N">关</el-radio> |
| | | </div> |
| | | .summary-pill { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | padding: 6px 10px; |
| | | border-radius: 999px; |
| | | background: #f7f9fc; |
| | | border: 1px solid #ebeef5; |
| | | font-size: 12px; |
| | | color: #606266; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>允许交管重新规划路径</div> |
| | | <el-radio border v-model="codeMap.trafficControlRestartCalcPath" label="Y">开</el-radio> |
| | | <el-radio border v-model="codeMap.trafficControlRestartCalcPath" label="N">关</el-radio> |
| | | </div> |
| | | .summary-pill strong { |
| | | font-size: 14px; |
| | | color: #303133; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>输出RCS调试日志</div> |
| | | <el-radio border v-model="codeMap.wcsDebugShowLog" label="true">开</el-radio> |
| | | <el-radio border v-model="codeMap.wcsDebugShowLog" label="false">关</el-radio> |
| | | </div> |
| | | </div> |
| | | .filter-item { |
| | | min-width: 220px; |
| | | flex: 1 1 260px; |
| | | } |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <el-button type="primary" @click="saveParam('shuttle')">保存</el-button> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | .group-title { |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | margin-bottom: 6px; |
| | | } |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <el-card class="box-card"> |
| | | <div slot="header" class="clearfix"> |
| | | <span>充电参数</span> |
| | | </div> |
| | | .code-cell { |
| | | font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; |
| | | color: #606266; |
| | | font-size: 12px; |
| | | line-height: 1.6; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | <div style="display: flex;flex-wrap: wrap;"> |
| | | <div class="show-box"> |
| | | <div>小车充电最大阈值</div> |
| | | <el-input v-model="codeMap.chargeMaxValue" style="width: 60%;"></el-input> |
| | | </div> |
| | | .config-name { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 4px; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>小车电量预警阈值</div> |
| | | <el-input v-model="codeMap.shuttlePowerEarlyValue" style="width: 60%;"></el-input> |
| | | </div> |
| | | .config-name-main { |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>小车满电校准</div> |
| | | <el-radio border v-model="codeMap.shuttleMaxPowerVerify" label="true">开</el-radio> |
| | | <el-radio border v-model="codeMap.shuttleMaxPowerVerify" label="false">关</el-radio> |
| | | </div> |
| | | .config-name-sub { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>定时充电开关</div> |
| | | <el-radio border v-model="codeMap.timedCharge" label="Y">开</el-radio> |
| | | <el-radio border v-model="codeMap.timedCharge" label="N">关</el-radio> |
| | | </div> |
| | | .editor-cell { |
| | | min-width: 320px; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>定时充电时间段</div> |
| | | <el-input v-model="codeMap.timedChargeRange" style="width: 60%;"></el-input> |
| | | </div> |
| | | .status-cell { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>小车默认充电线</div> |
| | | <el-input v-model="codeMap.shuttleDefaultChargePowerLine" style="width: 60%;"></el-input> |
| | | </div> |
| | | .empty-wrap { |
| | | padding: 32px 0 8px; |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>小车定时充电线</div> |
| | | <el-input v-model="codeMap.timedChargePowerLine" style="width: 60%;"></el-input> |
| | | </div> |
| | | </div> |
| | | .el-card__header { |
| | | padding: 16px 20px; |
| | | } |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <el-button type="primary" @click="saveParam('charge')">保存</el-button> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | .el-card__body { |
| | | padding: 18px 20px; |
| | | } |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <el-card class="box-card"> |
| | | <div slot="header" class="clearfix"> |
| | | <span>演示模式参数</span> |
| | | </div> |
| | | <div style="display: flex;flex-wrap: wrap;"> |
| | | <div class="show-box"> |
| | | <div>移动演示模式-楼层</div> |
| | | <el-input v-model="codeMap.demoRunLev" style="width: 60%;"></el-input> |
| | | </div> |
| | | .hero-card .el-card__body { |
| | | padding: 14px 18px; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>移动演示模式-是否换层</div> |
| | | <el-radio border v-model="codeMap.demoSwitchLev" label="Y">开</el-radio> |
| | | <el-radio border v-model="codeMap.demoSwitchLev" label="N">关</el-radio> |
| | | </div> |
| | | @media (max-width: 900px) { |
| | | body { |
| | | padding: 12px; |
| | | } |
| | | |
| | | <div class="show-box"> |
| | | <div>演示模式-货物搬运</div> |
| | | <el-radio border v-model="codeMap.demoCargoMove" label="Y">开</el-radio> |
| | | <el-radio border v-model="codeMap.demoCargoMove" label="N">关</el-radio> |
| | | </div> |
| | | .editor-cell { |
| | | min-width: 0; |
| | | } |
| | | } |
| | | </style> |
| | | </head> |
| | | |
| | | <div class="show-box"> |
| | | <div>演示模式-跑库</div> |
| | | <el-radio border v-model="codeMap.demoModeRunLoc" label="Y">开</el-radio> |
| | | <el-radio border v-model="codeMap.demoModeRunLoc" label="N">关</el-radio> |
| | | </div> |
| | | <body> |
| | | <div id="app" class="page-shell" v-loading="loading"> |
| | | <el-card shadow="never" class="hero-card"> |
| | | <div class="hero-header"> |
| | | <div> |
| | | <h1 class="hero-title">调试参数</h1> |
| | | <p class="hero-desc"> |
| | | 基于当前设备拓扑和 <code>sys_config</code> 实时展示运行参数。 |
| | | </p> |
| | | </div> |
| | | <div class="hero-actions"> |
| | | <el-button icon="el-icon-refresh" @click="reloadData">刷新数据</el-button> |
| | | <el-button type="primary" |
| | | icon="el-icon-check" |
| | | :loading="saveAllLoading" |
| | | :disabled="!hasChanges" |
| | | @click="saveAll"> |
| | | 保存全部变更 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | </div> |
| | | <div class="summary-strip"> |
| | | <div class="summary-pill" v-for="item in summaryCards" :key="item.key"> |
| | | <span>{{ item.label }}</span> |
| | | <strong>{{ item.value }}</strong> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <el-button type="primary" @click="saveParam('demo')">保存</el-button> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | </div> |
| | | <script> |
| | | var app = new Vue({ |
| | | el: '#app', |
| | | data: { |
| | | codeMap: {}, |
| | | }, |
| | | created() { |
| | | this.init() |
| | | }, |
| | | methods: { |
| | | init() { |
| | | this.getConfigData() |
| | | }, |
| | | getConfigData() { |
| | | let that = this; |
| | | $.ajax({ |
| | | url: baseUrl + "/config/listAll/auth", |
| | | headers: { |
| | | 'token': localStorage.getItem('token') |
| | | }, |
| | | data: {}, |
| | | dataType: 'json', |
| | | contentType: 'application/json;charset=UTF-8', |
| | | method: 'GET', |
| | | success: function(res) { |
| | | if (res.code == 200) { |
| | | let codeMap = {} |
| | | res.data.forEach((item) => { |
| | | codeMap[item.code] = item.value |
| | | }) |
| | | that.codeMap = codeMap; |
| | | } else if (res.code === 403) { |
| | | top.location.href = baseUrl + "/"; |
| | | } else { |
| | | that.$message({ |
| | | message: res.msg, |
| | | type: 'error' |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | saveParam(type) { |
| | | let that = this; |
| | | let codeList = this.getParamData(type) |
| | | let updateCodeList = []; |
| | | <el-card shadow="never" class="filter-card"> |
| | | <div class="filter-bar"> |
| | | <div class="filter-item"> |
| | | <el-input v-model.trim="searchText" |
| | | clearable |
| | | prefix-icon="el-icon-search" |
| | | placeholder="按配置名称或编码筛选"> |
| | | </el-input> |
| | | </div> |
| | | <div style="width: 240px;"> |
| | | <el-select v-model="selectedType" filterable style="width: 100%;"> |
| | | <el-option label="全部分组" value="all"></el-option> |
| | | <el-option v-for="type in selectTypeOptions" |
| | | :key="type" |
| | | :label="getGroupLabel(type)" |
| | | :value="type"> |
| | | </el-option> |
| | | </el-select> |
| | | </div> |
| | | <div> |
| | | <el-switch v-model="changedOnly" active-text="仅看已修改"></el-switch> |
| | | </div> |
| | | <div class="group-tags"> |
| | | <el-tag size="small">参数 {{ configList.length }} 项</el-tag> |
| | | <el-tag size="small">分组 {{ selectTypeOptions.length }} 个</el-tag> |
| | | <el-tag v-if="hasChanges" type="warning" size="small">待保存 {{ changedCount }} 项</el-tag> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | codeList.forEach((key) => { |
| | | let value = this.codeMap[key] |
| | | updateCodeList.push({ |
| | | code: key, |
| | | value: value |
| | | }) |
| | | }) |
| | | <div v-if="filteredGroups.length === 0" class="empty-wrap"> |
| | | <el-empty description="当前筛选条件下没有可显示的参数"></el-empty> |
| | | </div> |
| | | |
| | | console.log(updateCodeList) |
| | | <el-card v-for="group in filteredGroups" :key="group.type" shadow="never" class="group-card"> |
| | | <div slot="header" class="group-header"> |
| | | <div> |
| | | <div class="group-title">{{ group.label }}</div> |
| | | <p class="group-desc">{{ group.desc }}</p> |
| | | <div class="group-tags" style="margin-top: 10px;"> |
| | | <el-tag size="mini">{{ group.items.length }} 项</el-tag> |
| | | <el-tag v-if="group.deviceText" type="success" size="mini">{{ group.deviceText }}</el-tag> |
| | | <el-tag v-if="group.changedCount > 0" type="warning" size="mini">已修改 {{ group.changedCount }} 项</el-tag> |
| | | </div> |
| | | </div> |
| | | <div class="group-actions"> |
| | | <el-button size="mini" |
| | | :disabled="group.changedCount === 0" |
| | | :loading="saveGroupLoading[group.type]" |
| | | type="primary" |
| | | @click="saveGroup(group)"> |
| | | 保存本组 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | $.ajax({ |
| | | url: baseUrl + "/config/updateBatch", |
| | | headers: { |
| | | 'token': localStorage.getItem('token') |
| | | }, |
| | | data: JSON.stringify(updateCodeList), |
| | | dataType: 'json', |
| | | contentType: 'application/json;charset=UTF-8', |
| | | method: 'POST', |
| | | success: function(res) { |
| | | if (res.code == 200) { |
| | | that.$message({ |
| | | message: '保存成功', |
| | | type: 'success' |
| | | }); |
| | | } else if (res.code === 403) { |
| | | top.location.href = baseUrl + "/"; |
| | | } else { |
| | | that.$message({ |
| | | message: res.msg, |
| | | type: 'error' |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | getParamData(type) { |
| | | let data = [] |
| | | if(type == "shuttle") { |
| | | data.push('dispatchShuttleMaxNum'); |
| | | data.push('shuttleWrkInObligateCount'); |
| | | data.push('avoidInnerCircle'); |
| | | data.push('avoidOuterCircle'); |
| | | data.push('direction_map'); |
| | | data.push('shuttleDirectionReverse'); |
| | | data.push('shuttleOutLiftLocationDistance'); |
| | | data.push('shuttleMoveCommandsContinuously'); |
| | | data.push('trafficControlRestartCalcPath'); |
| | | data.push('wcsDebugShowLog'); |
| | | }else if (type == "charge") { |
| | | data.push('chargeMaxValue'); |
| | | data.push('shuttlePowerEarlyValue'); |
| | | data.push('shuttleMaxPowerVerify'); |
| | | data.push('timedCharge'); |
| | | data.push('timedChargeRange'); |
| | | data.push('shuttleDefaultChargePowerLine'); |
| | | data.push('timedChargePowerLine'); |
| | | }else if (type == "demo") { |
| | | data.push('demoRunLev'); |
| | | data.push('demoSwitchLev'); |
| | | data.push('demoCargoMove'); |
| | | data.push('demoModeRunLoc'); |
| | | } |
| | | <el-table :data="group.items" border stripe size="mini"> |
| | | <el-table-column label="参数" min-width="260"> |
| | | <template slot-scope="scope"> |
| | | <div class="config-name"> |
| | | <span class="config-name-main">{{ scope.row.name || scope.row.code }}</span> |
| | | <span class="config-name-sub"> |
| | | {{ getValueTypeText(scope.row) }} |
| | | <span v-if="scope.row.status === 0"> / 已禁用</span> |
| | | </span> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | return data; |
| | | }, |
| | | }, |
| | | }) |
| | | </script> |
| | | </body> |
| | | <el-table-column label="编码" min-width="210"> |
| | | <template slot-scope="scope"> |
| | | <div class="code-cell">{{ scope.row.code }}</div> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column label="当前值" min-width="420"> |
| | | <template slot-scope="scope"> |
| | | <div class="editor-cell"> |
| | | <el-radio-group v-if="resolveEditor(scope.row) === 'boolean'" |
| | | v-model="codeMap[scope.row.code]" |
| | | size="mini"> |
| | | <el-radio-button v-for="option in getBooleanOptions(scope.row)" |
| | | :key="option.value" |
| | | :label="option.value"> |
| | | {{ option.label }} |
| | | </el-radio-button> |
| | | </el-radio-group> |
| | | |
| | | <el-input v-else-if="resolveEditor(scope.row) === 'textarea'" |
| | | v-model="codeMap[scope.row.code]" |
| | | type="textarea" |
| | | resize="vertical" |
| | | :autosize="{ minRows: 2, maxRows: 5 }"> |
| | | </el-input> |
| | | |
| | | <el-input v-else |
| | | v-model="codeMap[scope.row.code]" |
| | | clearable> |
| | | </el-input> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column label="状态" width="170"> |
| | | <template slot-scope="scope"> |
| | | <div class="status-cell"> |
| | | <el-tag v-if="isChanged(scope.row.code)" type="warning" size="mini">待保存</el-tag> |
| | | <el-tag v-else type="success" size="mini">已同步</el-tag> |
| | | <el-button v-if="isChanged(scope.row.code)" |
| | | type="text" |
| | | size="mini" |
| | | @click="resetConfig(scope.row.code)"> |
| | | 还原 |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <script> |
| | | new Vue({ |
| | | el: '#app', |
| | | data: { |
| | | loading: false, |
| | | saveAllLoading: false, |
| | | saveGroupLoading: {}, |
| | | configList: [], |
| | | deviceList: [], |
| | | codeMap: {}, |
| | | originalCodeMap: {}, |
| | | searchText: '', |
| | | selectedType: 'all', |
| | | changedOnly: false, |
| | | groupOrder: ['system', 'solver', 'crn', 'fake', 'notify', 'String', 'other'] |
| | | }, |
| | | computed: { |
| | | selectTypeOptions: function () { |
| | | var typeMap = {}; |
| | | (this.configList || []).forEach(function (item) { |
| | | var type = item && item.selectType ? item.selectType : 'other'; |
| | | typeMap[type] = true; |
| | | }); |
| | | return this.sortTypes(Object.keys(typeMap)); |
| | | }, |
| | | deviceTypeCountMap: function () { |
| | | var result = {}; |
| | | (this.deviceList || []).forEach(function (item) { |
| | | if (!item || !item.deviceType) { |
| | | return; |
| | | } |
| | | result[item.deviceType] = (result[item.deviceType] || 0) + 1; |
| | | }); |
| | | return result; |
| | | }, |
| | | fakeDeviceCount: function () { |
| | | var total = 0; |
| | | (this.deviceList || []).forEach(function (item) { |
| | | if (item && String(item.fake) === '1') { |
| | | total += 1; |
| | | } |
| | | }); |
| | | return total; |
| | | }, |
| | | changedCount: function () { |
| | | var self = this; |
| | | var total = 0; |
| | | Object.keys(this.codeMap || {}).forEach(function (code) { |
| | | if (self.isChanged(code)) { |
| | | total += 1; |
| | | } |
| | | }); |
| | | return total; |
| | | }, |
| | | hasChanges: function () { |
| | | return this.changedCount > 0; |
| | | }, |
| | | summaryCards: function () { |
| | | return [ |
| | | { |
| | | key: 'crn', |
| | | label: '堆垛机', |
| | | value: this.deviceTypeCountMap.Crn || 0, |
| | | hint: '当前已配置的单工位堆垛机数量' |
| | | }, |
| | | { |
| | | key: 'dualCrn', |
| | | label: '双工位堆垛机', |
| | | value: this.deviceTypeCountMap.DualCrn || 0, |
| | | hint: '当前已配置的双工位堆垛机数量' |
| | | }, |
| | | { |
| | | key: 'devp', |
| | | label: '输送站控制器', |
| | | value: this.deviceTypeCountMap.Devp || 0, |
| | | hint: '当前站台/输送线控制器数量' |
| | | }, |
| | | { |
| | | key: 'rgv', |
| | | label: 'RGV', |
| | | value: this.deviceTypeCountMap.Rgv || 0, |
| | | hint: '当前 RGV 控制器数量' |
| | | }, |
| | | { |
| | | key: 'fake', |
| | | label: '仿真设备', |
| | | value: this.fakeDeviceCount, |
| | | hint: 'device_config 中 fake=1 的设备数' |
| | | }, |
| | | { |
| | | key: 'groups', |
| | | label: '配置分组', |
| | | value: this.selectTypeOptions.length, |
| | | hint: '来自 sys_config.select_type' |
| | | }, |
| | | { |
| | | key: 'configs', |
| | | label: '参数总数', |
| | | value: this.configList.length, |
| | | hint: '当前页面纳入管理的 sys_config 数量' |
| | | }, |
| | | { |
| | | key: 'changed', |
| | | label: '待保存变更', |
| | | value: this.changedCount, |
| | | hint: this.changedCount > 0 ? '存在尚未提交的参数改动' : '当前没有未保存的改动' |
| | | } |
| | | ]; |
| | | }, |
| | | filteredGroups: function () { |
| | | var self = this; |
| | | var groups = {}; |
| | | var keyword = (this.searchText || '').toLowerCase(); |
| | | var list = Array.isArray(this.configList) ? this.configList.slice() : []; |
| | | list.sort(function (a, b) { |
| | | return (a.id || 0) - (b.id || 0); |
| | | }); |
| | | |
| | | list.forEach(function (item) { |
| | | if (!item || !item.code) { |
| | | return; |
| | | } |
| | | var type = item.selectType || 'other'; |
| | | if (self.selectedType !== 'all' && self.selectedType !== type) { |
| | | return; |
| | | } |
| | | if (keyword) { |
| | | var text = ((item.name || '') + ' ' + item.code).toLowerCase(); |
| | | if (text.indexOf(keyword) === -1) { |
| | | return; |
| | | } |
| | | } |
| | | if (self.changedOnly && !self.isChanged(item.code)) { |
| | | return; |
| | | } |
| | | if (!groups[type]) { |
| | | groups[type] = { |
| | | type: type, |
| | | label: self.getGroupLabel(type), |
| | | desc: self.getGroupDesc(type), |
| | | deviceText: self.getGroupDeviceText(type), |
| | | items: [], |
| | | changedCount: 0 |
| | | }; |
| | | } |
| | | groups[type].items.push(item); |
| | | if (self.isChanged(item.code)) { |
| | | groups[type].changedCount += 1; |
| | | } |
| | | }); |
| | | |
| | | return self.sortTypes(Object.keys(groups)).map(function (type) { |
| | | return groups[type]; |
| | | }); |
| | | } |
| | | }, |
| | | created: function () { |
| | | this.reloadData(); |
| | | }, |
| | | methods: { |
| | | reloadData: function () { |
| | | var self = this; |
| | | var pending = 2; |
| | | var failed = false; |
| | | self.loading = true; |
| | | |
| | | function finish() { |
| | | pending -= 1; |
| | | if (pending <= 0) { |
| | | self.loading = false; |
| | | if (failed) { |
| | | self.$message.error('部分数据加载失败'); |
| | | } |
| | | } |
| | | } |
| | | |
| | | $.ajax({ |
| | | url: baseUrl + "/config/listAll/auth", |
| | | headers: self.authHeaders(), |
| | | dataType: 'json', |
| | | method: 'GET', |
| | | success: function (res) { |
| | | if (self.handleForbidden(res)) { |
| | | return; |
| | | } |
| | | if (!res || res.code !== 200 || !Array.isArray(res.data)) { |
| | | failed = true; |
| | | finish(); |
| | | return; |
| | | } |
| | | self.configList = res.data.slice(); |
| | | self.rebuildCodeMaps(); |
| | | finish(); |
| | | }, |
| | | error: function () { |
| | | failed = true; |
| | | finish(); |
| | | } |
| | | }); |
| | | |
| | | $.ajax({ |
| | | url: baseUrl + "/deviceConfig/list/auth", |
| | | headers: self.authHeaders(), |
| | | dataType: 'json', |
| | | method: 'GET', |
| | | data: { |
| | | curr: 1, |
| | | limit: 200 |
| | | }, |
| | | success: function (res) { |
| | | if (self.handleForbidden(res)) { |
| | | return; |
| | | } |
| | | if (!res || res.code !== 200 || !res.data) { |
| | | failed = true; |
| | | finish(); |
| | | return; |
| | | } |
| | | self.deviceList = Array.isArray(res.data.records) ? res.data.records : []; |
| | | finish(); |
| | | }, |
| | | error: function () { |
| | | failed = true; |
| | | finish(); |
| | | } |
| | | }); |
| | | }, |
| | | rebuildCodeMaps: function () { |
| | | var current = {}; |
| | | (this.configList || []).forEach(function (item) { |
| | | if (item && item.code) { |
| | | current[item.code] = item.value == null ? '' : String(item.value); |
| | | } |
| | | }); |
| | | this.codeMap = $.extend({}, current); |
| | | this.originalCodeMap = $.extend({}, current); |
| | | }, |
| | | authHeaders: function () { |
| | | return { |
| | | token: localStorage.getItem('token') |
| | | }; |
| | | }, |
| | | handleForbidden: function (res) { |
| | | if (res && res.code === 403) { |
| | | top.location.href = baseUrl + "/"; |
| | | return true; |
| | | } |
| | | return false; |
| | | }, |
| | | sortTypes: function (types) { |
| | | var self = this; |
| | | return (types || []).slice().sort(function (a, b) { |
| | | var aIndex = self.groupOrder.indexOf(a); |
| | | var bIndex = self.groupOrder.indexOf(b); |
| | | if (aIndex === -1) { |
| | | aIndex = self.groupOrder.length + 100; |
| | | } |
| | | if (bIndex === -1) { |
| | | bIndex = self.groupOrder.length + 100; |
| | | } |
| | | if (aIndex !== bIndex) { |
| | | return aIndex - bIndex; |
| | | } |
| | | return String(a).localeCompare(String(b)); |
| | | }); |
| | | }, |
| | | getGroupLabel: function (type) { |
| | | var labels = { |
| | | system: '系统运行参数', |
| | | solver: '求解器与调度参数', |
| | | crn: '堆垛机控制参数', |
| | | fake: '仿真模式参数', |
| | | notify: '通知上报参数', |
| | | String: '站点路径参数', |
| | | other: '其他参数' |
| | | }; |
| | | return labels[type] || type; |
| | | }, |
| | | getGroupDesc: function (type) { |
| | | var descMap = { |
| | | system: '覆盖 WMS 联动、设备日志、输送线任务和监控地图等系统级参数。', |
| | | solver: '用于堆垛机调度与求解器执行,包括速度、权重、最大求解时长等。', |
| | | crn: '针对堆垛机控制链路的调试参数,优先用于异常回滚和执行保护。', |
| | | fake: '仿真开关与任务生成策略,适合联调或演示环境。', |
| | | notify: '外部消息通知、钉钉告警和 WMS 回调重试策略。', |
| | | String: '站点路径评分与默认模板选择,影响输送站点寻径结果。', |
| | | other: '当前未归类的配置项。' |
| | | }; |
| | | return descMap[type] || '当前分组未配置说明。'; |
| | | }, |
| | | getGroupDeviceText: function (type) { |
| | | var crnCount = this.deviceTypeCountMap.Crn || 0; |
| | | var dualCrnCount = this.deviceTypeCountMap.DualCrn || 0; |
| | | var devpCount = this.deviceTypeCountMap.Devp || 0; |
| | | var rgvCount = this.deviceTypeCountMap.Rgv || 0; |
| | | if (type === 'system') { |
| | | return '设备范围:堆垛机 ' + crnCount + ' 台 / 双工位 ' + dualCrnCount + ' 台 / 输送站控制器 ' + devpCount + ' 台 / RGV ' + rgvCount + ' 台'; |
| | | } |
| | | if (type === 'solver' || type === 'crn') { |
| | | return '关联堆垛机:单工位 ' + crnCount + ' 台 / 双工位 ' + dualCrnCount + ' 台'; |
| | | } |
| | | if (type === 'fake') { |
| | | return '仿真设备:' + this.fakeDeviceCount + ' 台'; |
| | | } |
| | | if (type === 'notify') { |
| | | return '外部链路:WMS / 钉钉 / 通知重试'; |
| | | } |
| | | if (type === 'String') { |
| | | return '路径配置:输送站点评分与模板'; |
| | | } |
| | | return ''; |
| | | }, |
| | | getValueTypeText: function (config) { |
| | | if (config && config.type === 2) { |
| | | return 'JSON'; |
| | | } |
| | | if (this.resolveEditor(config) === 'boolean') { |
| | | return '布尔开关'; |
| | | } |
| | | if (this.resolveEditor(config) === 'textarea') { |
| | | return '长文本'; |
| | | } |
| | | return '字符串'; |
| | | }, |
| | | resolveEditor: function (config) { |
| | | var value = this.getCodeValue(config.code); |
| | | if (config && config.type === 2) { |
| | | return 'textarea'; |
| | | } |
| | | if (this.isBooleanValue(value)) { |
| | | return 'boolean'; |
| | | } |
| | | if ((config && /url|uri|path/i.test(config.code || '')) || String(value || '').length > 60) { |
| | | return 'textarea'; |
| | | } |
| | | return 'input'; |
| | | }, |
| | | isBooleanValue: function (value) { |
| | | var text = value == null ? '' : String(value); |
| | | return text === 'Y' || text === 'N' || text === 'true' || text === 'false'; |
| | | }, |
| | | getBooleanOptions: function (config) { |
| | | var value = this.getCodeValue(config.code); |
| | | if (String(value) === 'Y' || String(value) === 'N') { |
| | | return [ |
| | | { label: '开', value: 'Y' }, |
| | | { label: '关', value: 'N' } |
| | | ]; |
| | | } |
| | | return [ |
| | | { label: '开', value: 'true' }, |
| | | { label: '关', value: 'false' } |
| | | ]; |
| | | }, |
| | | getCodeValue: function (code) { |
| | | if (!code) { |
| | | return ''; |
| | | } |
| | | return this.codeMap[code] == null ? '' : String(this.codeMap[code]); |
| | | }, |
| | | isChanged: function (code) { |
| | | return this.getCodeValue(code) !== (this.originalCodeMap[code] == null ? '' : String(this.originalCodeMap[code])); |
| | | }, |
| | | resetConfig: function (code) { |
| | | this.$set(this.codeMap, code, this.originalCodeMap[code] == null ? '' : String(this.originalCodeMap[code])); |
| | | }, |
| | | collectChangedItems: function (items) { |
| | | var self = this; |
| | | return (items || []).filter(function (item) { |
| | | return item && item.code && self.isChanged(item.code); |
| | | }); |
| | | }, |
| | | validateItems: function (items) { |
| | | var error = ''; |
| | | (items || []).some(function (item) { |
| | | if (!item) { |
| | | return false; |
| | | } |
| | | if (item.type === 2) { |
| | | try { |
| | | JSON.parse(item.value); |
| | | } catch (e) { |
| | | error = (item.name || item.code) + ' JSON 格式不正确'; |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | }); |
| | | return error; |
| | | }, |
| | | doSave: function (items, saveKey) { |
| | | var self = this; |
| | | var changedItems = self.collectChangedItems(items); |
| | | if (changedItems.length === 0) { |
| | | self.$message({ |
| | | message: '当前没有需要保存的变更', |
| | | type: 'info' |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | var payload = changedItems.map(function (item) { |
| | | return { |
| | | id: item.id, |
| | | name: item.name, |
| | | code: item.code, |
| | | value: self.getCodeValue(item.code), |
| | | type: item.type, |
| | | status: item.status, |
| | | selectType: item.selectType |
| | | }; |
| | | }); |
| | | |
| | | var validateMsg = self.validateItems(payload); |
| | | if (validateMsg) { |
| | | self.$message({ |
| | | message: validateMsg, |
| | | type: 'error' |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | if (saveKey === 'all') { |
| | | self.saveAllLoading = true; |
| | | } else { |
| | | self.$set(self.saveGroupLoading, saveKey, true); |
| | | } |
| | | |
| | | $.ajax({ |
| | | url: baseUrl + "/config/updateBatch", |
| | | headers: self.authHeaders(), |
| | | data: JSON.stringify(payload.map(function (item) { |
| | | return { |
| | | code: item.code, |
| | | value: item.value |
| | | }; |
| | | })), |
| | | dataType: 'json', |
| | | contentType: 'application/json;charset=UTF-8', |
| | | method: 'POST', |
| | | success: function (res) { |
| | | if (saveKey === 'all') { |
| | | self.saveAllLoading = false; |
| | | } else { |
| | | self.$set(self.saveGroupLoading, saveKey, false); |
| | | } |
| | | if (self.handleForbidden(res)) { |
| | | return; |
| | | } |
| | | if (!res || res.code !== 200) { |
| | | self.$message({ |
| | | message: res && res.msg ? res.msg : '保存失败', |
| | | type: 'error' |
| | | }); |
| | | return; |
| | | } |
| | | payload.forEach(function (item) { |
| | | self.originalCodeMap[item.code] = item.value == null ? '' : String(item.value); |
| | | }); |
| | | self.$message({ |
| | | message: '保存成功,运行缓存已同步刷新', |
| | | type: 'success' |
| | | }); |
| | | }, |
| | | error: function () { |
| | | if (saveKey === 'all') { |
| | | self.saveAllLoading = false; |
| | | } else { |
| | | self.$set(self.saveGroupLoading, saveKey, false); |
| | | } |
| | | self.$message({ |
| | | message: '保存失败', |
| | | type: 'error' |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | saveGroup: function (group) { |
| | | this.doSave(group.items, group.type); |
| | | }, |
| | | saveAll: function () { |
| | | this.doSave(this.configList, 'all'); |
| | | } |
| | | } |
| | | }); |
| | | </script> |
| | | </body> |
| | | |
| | | </html> |