function createDefaultProfileConfig() {
|
return {
|
calcMaxDepth: 120,
|
calcMaxPaths: 500,
|
calcMaxCost: 300,
|
s1TopK: 5,
|
s1LenWeight: 1.0,
|
s1TurnWeight: 3.0,
|
s1LiftWeight: 8.0,
|
s1SoftDeviationWeight: 4.0,
|
s1MaxLenRatio: 1.15,
|
s1MaxTurnDiff: 1,
|
s2BusyWeight: 2.0,
|
s2RunBlockWeight: 10.0,
|
s2LoopLoadWeight: 12.0
|
}
|
}
|
|
function createDefaultProfile() {
|
return {
|
id: null,
|
profileCode: '',
|
profileName: '',
|
priority: 100,
|
status: 1,
|
memo: '',
|
config: createDefaultProfileConfig(),
|
_originCode: null
|
}
|
}
|
|
function createDefaultRule(defaultProfileCode) {
|
return {
|
id: null,
|
ruleCode: '',
|
ruleName: '',
|
priority: 100,
|
status: 1,
|
sceneType: 'station',
|
startStationId: null,
|
endStationId: null,
|
profileCode: defaultProfileCode || 'default',
|
memo: '',
|
hard: {
|
mustPassStations: [],
|
forbidStations: [],
|
mustPassEdges: [],
|
forbidEdges: [],
|
mustPassEdgesText: '',
|
forbidEdgesText: ''
|
},
|
waypoint: {
|
stations: []
|
},
|
soft: {
|
keyStations: [],
|
preferredPath: [],
|
deviationWeight: 6.0,
|
maxOffPathCount: 2
|
},
|
fallback: {
|
strictWaypoint: false,
|
allowSoftDegrade: true
|
},
|
_originCode: null
|
}
|
}
|
|
var app = new Vue({
|
el: '#app',
|
data: function () {
|
return {
|
loading: false,
|
saving: false,
|
scoreMode: 'legacy',
|
defaultProfileCode: 'default',
|
profiles: [],
|
rules: [],
|
stations: [],
|
levList: [],
|
selectedProfileCode: '',
|
selectedRuleCode: '',
|
profileDialogVisible: false,
|
ruleDialogVisible: false,
|
profileForm: createDefaultProfile(),
|
ruleForm: createDefaultRule('default'),
|
previewForm: {
|
startStationId: null,
|
endStationId: null
|
},
|
previewLoading: false,
|
previewResult: null,
|
activeMapLev: null,
|
mapContext: {
|
lev: null,
|
width: 0,
|
height: 0,
|
nodes: [],
|
nodeMap: {}
|
},
|
mapZoomPercent: 100,
|
pickedStationId: null,
|
showRuleJson: false,
|
showAllPathTags: false,
|
softExpandLoading: false,
|
mapDragActive: false,
|
mapDragMoved: false,
|
mapDragStartX: 0,
|
mapDragStartY: 0,
|
mapDragOriginPanX: 0,
|
mapDragOriginPanY: 0,
|
suppressNodeClick: false,
|
mapPanX: 20,
|
mapPanY: 20
|
}
|
},
|
computed: {
|
pickedStation: function () {
|
return this.findStation(this.pickedStationId)
|
},
|
hasPickedStation: function () {
|
return this.pickedStationId != null
|
},
|
stationMapById: function () {
|
var map = {}
|
;(this.stations || []).forEach(function (station) {
|
if (station && station.stationId != null) {
|
map[String(station.stationId)] = station
|
}
|
})
|
return map
|
},
|
stationOptions: function () {
|
return (this.stations || []).map(function (station) {
|
return {
|
stationId: station.stationId,
|
label: this.stationOptionLabel(station)
|
}
|
}.bind(this))
|
},
|
selectedRule: function () {
|
var code = this.selectedRuleCode
|
if (!code) {
|
return null
|
}
|
for (var i = 0; i < this.rules.length; i++) {
|
if (this.rules[i].ruleCode === code) {
|
return this.rules[i]
|
}
|
}
|
return null
|
},
|
activeRuleForVisual: function () {
|
if (this.ruleDialogVisible && this.ruleForm) {
|
return this.ruleForm
|
}
|
if (this.selectedRule) {
|
return this.selectedRule
|
}
|
if (this.previewResult && this.previewResult.resolvedPolicy && this.previewResult.resolvedPolicy.ruleConfig) {
|
return {
|
startStationId: this.previewForm.startStationId,
|
endStationId: this.previewForm.endStationId,
|
hard: this.previewResult.resolvedPolicy.ruleConfig.hard || this.defaultRule().hard,
|
waypoint: this.previewResult.resolvedPolicy.ruleConfig.waypoint || this.defaultRule().waypoint,
|
soft: this.previewResult.resolvedPolicy.ruleConfig.soft || this.defaultRule().soft,
|
fallback: this.previewResult.resolvedPolicy.ruleConfig.fallback || this.defaultRule().fallback
|
}
|
}
|
return null
|
},
|
previewPathTags: function () {
|
var result = this.previewResult
|
if (!result || !result.pathStationIds) {
|
return []
|
}
|
return result.pathStationIds.map(function (stationId) {
|
return {
|
stationId: stationId,
|
label: this.stationLabel(stationId)
|
}
|
}.bind(this))
|
},
|
visiblePreviewPathTags: function () {
|
if (this.showAllPathTags) {
|
return this.previewPathTags
|
}
|
return this.previewPathTags.slice(0, 14)
|
},
|
hiddenPathTagCount: function () {
|
return Math.max(this.previewPathTags.length - this.visiblePreviewPathTags.length, 0)
|
},
|
hasActualPath: function () {
|
return !!this.actualPathPolyline
|
},
|
hasPreviewPath: function () {
|
return !!(this.previewResult && this.previewResult.pathStationIds && this.previewResult.pathStationIds.length)
|
},
|
pathStationLookup: function () {
|
return this.buildLookup(this.previewResult && this.previewResult.pathStationIds)
|
},
|
preferredStationLookup: function () {
|
return this.buildLookup(this.activeRuleForVisual && this.activeRuleForVisual.soft ? this.activeRuleForVisual.soft.preferredPath : [])
|
},
|
waypointStationLookup: function () {
|
return this.buildLookup(this.activeRuleForVisual && this.activeRuleForVisual.waypoint ? this.activeRuleForVisual.waypoint.stations : [])
|
},
|
forbidStationLookup: function () {
|
return this.buildLookup(this.activeRuleForVisual && this.activeRuleForVisual.hard ? this.activeRuleForVisual.hard.forbidStations : [])
|
},
|
mustPassStationLookup: function () {
|
return this.buildLookup(this.activeRuleForVisual && this.activeRuleForVisual.hard ? this.activeRuleForVisual.hard.mustPassStations : [])
|
},
|
renderedMapNodes: function () {
|
var stationMap = this.stationMapById
|
var pickedId = String(this.pickedStationId == null ? '' : this.pickedStationId)
|
var startId = String(this.previewForm.startStationId == null ? '' : this.previewForm.startStationId)
|
var endId = String(this.previewForm.endStationId == null ? '' : this.previewForm.endStationId)
|
var pathLookup = this.pathStationLookup
|
var preferredLookup = this.preferredStationLookup
|
var waypointLookup = this.waypointStationLookup
|
var forbidLookup = this.forbidStationLookup
|
var mustPassLookup = this.mustPassStationLookup
|
return (this.mapContext.nodes || []).map(function (node) {
|
var stationId = node.stationId
|
var key = String(stationId == null ? '' : stationId)
|
var classes = []
|
if (pickedId && pickedId === key) {
|
classes.push('is-picked')
|
}
|
if (startId && startId === key) {
|
classes.push('is-start')
|
}
|
if (endId && endId === key) {
|
classes.push('is-end')
|
}
|
if (pathLookup[key]) {
|
classes.push('is-path')
|
}
|
if (preferredLookup[key]) {
|
classes.push('is-preferred')
|
}
|
if (waypointLookup[key]) {
|
classes.push('is-waypoint')
|
}
|
if (forbidLookup[key]) {
|
classes.push('is-forbid')
|
}
|
if (mustPassLookup[key]) {
|
classes.push('is-must-pass')
|
}
|
var station = stationMap[key]
|
return {
|
stationId: stationId,
|
x: node.x,
|
y: node.y,
|
left: node.x + 'px',
|
top: node.y + 'px',
|
classes: classes,
|
title: station ? this.stationOptionLabel(station) : String(stationId || ''),
|
showLabel: !!(startId === key || endId === key || pathLookup[key] || waypointLookup[key] || forbidLookup[key] || mustPassLookup[key] || pickedId === key)
|
}
|
}.bind(this))
|
},
|
mapStageStyle: function () {
|
return {
|
width: this.mapContext.width + 'px',
|
height: this.mapContext.height + 'px',
|
transform: 'translate(' + this.mapPanX + 'px, ' + this.mapPanY + 'px) scale(' + (this.mapZoomPercent / 100) + ')'
|
}
|
},
|
actualPathPolyline: function () {
|
var stationIds = this.previewResult && this.previewResult.pathStationIds ? this.previewResult.pathStationIds : []
|
return this.buildPolyline(stationIds)
|
},
|
preferredPathPolyline: function () {
|
var rule = this.activeRuleForVisual
|
var preferredPath = rule && rule.soft ? rule.soft.preferredPath : []
|
return this.buildPolyline(preferredPath)
|
},
|
activeRulePreviewJson: function () {
|
if (!this.activeRuleForVisual) {
|
return ''
|
}
|
return JSON.stringify(this.sanitizeRuleForSave(this.activeRuleForVisual), null, 2)
|
},
|
ruleDialogPickedHint: function () {
|
if (!this.ruleDialogVisible) {
|
return ''
|
}
|
if (!this.pickedStation) {
|
return '规则弹窗开启时,仍可在右侧地图点击站点,再一键加入必经/禁用/途经/偏好。'
|
}
|
return '当前地图选中:' + this.stationOptionLabel(this.pickedStation)
|
}
|
},
|
mounted: function () {
|
this.loadData()
|
},
|
beforeDestroy: function () {
|
this.detachMapDragListeners()
|
},
|
methods: {
|
loadData: function () {
|
var that = this
|
this.loading = true
|
$.ajax({
|
url: baseUrl + '/basStationPathPolicy/data/auth',
|
method: 'GET',
|
headers: { token: localStorage.getItem('token') },
|
success: function (res) {
|
that.loading = false
|
if (res.code !== 200) {
|
that.$message.error('加载失败: ' + res.msg)
|
return
|
}
|
var data = res.data || {}
|
that.scoreMode = data.scoreMode || 'legacy'
|
that.defaultProfileCode = data.defaultProfileCode || 'default'
|
that.showRuleJson = false
|
that.showAllPathTags = false
|
that.stations = (data.stations || []).sort(function (a, b) {
|
if ((a.stationLev || 0) !== (b.stationLev || 0)) {
|
return (a.stationLev || 0) - (b.stationLev || 0)
|
}
|
return (a.stationId || 0) - (b.stationId || 0)
|
})
|
that.levList = data.levList || []
|
that.profiles = (data.profiles || []).map(that.normalizeProfile)
|
that.rules = (data.rules || []).map(that.normalizeRule)
|
if (!that.defaultProfileCode && that.profiles.length) {
|
that.defaultProfileCode = that.profiles[0].profileCode
|
}
|
if (!that.selectedProfileCode && that.profiles.length) {
|
that.selectedProfileCode = that.defaultProfileCode || that.profiles[0].profileCode
|
}
|
if (!that.selectedRuleCode && that.rules.length) {
|
that.selectedRuleCode = that.rules[0].ruleCode
|
}
|
if (that.selectedRule) {
|
that.loadMapByRule(that.selectedRule)
|
} else if (that.levList.length && !that.activeMapLev) {
|
that.loadMapByLev(that.levList[0])
|
}
|
},
|
error: function () {
|
that.loading = false
|
that.$message.error('加载请求异常')
|
}
|
})
|
},
|
saveAll: function () {
|
if (!this.profiles.length) {
|
this.$message.warning('至少需要保留一个模板')
|
return
|
}
|
var hasDefault = this.profiles.some(function (item) {
|
return item.profileCode === this.defaultProfileCode
|
}.bind(this))
|
if (!hasDefault) {
|
this.$message.warning('默认模板编码没有对应模板')
|
return
|
}
|
var payload = {
|
scoreMode: this.scoreMode,
|
defaultProfileCode: this.defaultProfileCode,
|
profiles: this.profiles.map(this.sanitizeProfileForSave),
|
rules: this.rules.map(this.sanitizeRuleForSave)
|
}
|
var that = this
|
this.saving = true
|
$.ajax({
|
url: baseUrl + '/basStationPathPolicy/save/auth',
|
method: 'POST',
|
headers: { token: localStorage.getItem('token') },
|
contentType: 'application/json',
|
data: JSON.stringify(payload),
|
success: function (res) {
|
that.saving = false
|
if (res.code !== 200) {
|
that.$message.error('保存失败: ' + res.msg)
|
return
|
}
|
that.$message.success('保存成功')
|
that.loadData()
|
},
|
error: function () {
|
that.saving = false
|
that.$message.error('保存请求异常')
|
}
|
})
|
},
|
openProfileDialog: function (item) {
|
this.profileForm = item ? this.cloneProfileModel(item) : this.defaultProfile()
|
this.profileDialogVisible = true
|
},
|
confirmProfileDialog: function () {
|
var form = this.profileForm
|
if (this.isBlank(form.profileCode)) {
|
this.$message.warning('模板编码不能为空')
|
return
|
}
|
if (this.isBlank(form.profileName)) {
|
this.$message.warning('模板名称不能为空')
|
return
|
}
|
var existsIndex = this.findProfileIndex(form.profileCode)
|
if (existsIndex >= 0 && (!form._originCode || form._originCode !== form.profileCode)) {
|
this.$message.warning('模板编码已存在')
|
return
|
}
|
var profile = this.cloneProfileModel(form)
|
delete profile._originCode
|
if (form._originCode) {
|
var originIndex = this.findProfileIndex(form._originCode)
|
if (originIndex >= 0) {
|
this.$set(this.profiles, originIndex, profile)
|
if (this.defaultProfileCode === form._originCode) {
|
this.defaultProfileCode = profile.profileCode
|
}
|
if (this.selectedProfileCode === form._originCode) {
|
this.selectedProfileCode = profile.profileCode
|
}
|
this.rules.forEach(function (rule) {
|
if (rule.profileCode === form._originCode) {
|
rule.profileCode = profile.profileCode
|
}
|
})
|
}
|
} else {
|
this.profiles.push(profile)
|
this.selectedProfileCode = profile.profileCode
|
if (!this.defaultProfileCode) {
|
this.defaultProfileCode = profile.profileCode
|
}
|
}
|
this.profileDialogVisible = false
|
},
|
cloneProfile: function (item) {
|
var copy = this.cloneProfileModel(item)
|
copy.profileCode = item.profileCode + '_copy'
|
copy.profileName = item.profileName + ' - 副本'
|
copy._originCode = null
|
this.profileForm = copy
|
this.profileDialogVisible = true
|
},
|
removeProfile: function (item) {
|
if (!item) {
|
return
|
}
|
if (this.profiles.length <= 1) {
|
this.$message.warning('至少保留一个模板')
|
return
|
}
|
var used = this.rules.some(function (rule) {
|
return rule.profileCode === item.profileCode
|
})
|
if (used) {
|
this.$message.warning('该模板仍被规则引用,不能删除')
|
return
|
}
|
var that = this
|
this.$confirm('确认删除模板 ' + item.profileCode + ' 吗?', '提示', { type: 'warning' })
|
.then(function () {
|
that.profiles = that.profiles.filter(function (profile) {
|
return profile.profileCode !== item.profileCode
|
})
|
if (that.defaultProfileCode === item.profileCode) {
|
that.defaultProfileCode = that.profiles[0] ? that.profiles[0].profileCode : ''
|
}
|
if (that.selectedProfileCode === item.profileCode) {
|
that.selectedProfileCode = that.defaultProfileCode
|
}
|
})
|
.catch(function () {})
|
},
|
openRuleDialog: function (item) {
|
this.ruleForm = item ? this.cloneRuleModel(item) : this.defaultRule()
|
this.ruleDialogVisible = true
|
},
|
confirmRuleDialog: function () {
|
var form = this.ruleForm
|
if (this.isBlank(form.ruleCode)) {
|
this.$message.warning('规则编码不能为空')
|
return
|
}
|
if (this.isBlank(form.ruleName)) {
|
this.$message.warning('规则名称不能为空')
|
return
|
}
|
var existsIndex = this.findRuleIndex(form.ruleCode)
|
if (existsIndex >= 0 && (!form._originCode || form._originCode !== form.ruleCode)) {
|
this.$message.warning('规则编码已存在')
|
return
|
}
|
var rule = this.cloneRuleModel(form)
|
delete rule._originCode
|
if (form._originCode) {
|
var originIndex = this.findRuleIndex(form._originCode)
|
if (originIndex >= 0) {
|
this.$set(this.rules, originIndex, rule)
|
if (this.selectedRuleCode === form._originCode) {
|
this.selectedRuleCode = rule.ruleCode
|
}
|
}
|
} else {
|
this.rules.push(rule)
|
this.selectedRuleCode = rule.ruleCode
|
}
|
this.ruleDialogVisible = false
|
this.loadMapByRule(rule)
|
},
|
importPreviewPathToRule: function () {
|
if (!this.hasPreviewPath) {
|
this.$message.warning('请先在右侧完成一次路径预览')
|
return
|
}
|
if (!this.ruleForm || !this.ruleForm.soft) {
|
return
|
}
|
this.ruleForm.soft.preferredPath = (this.previewResult.pathStationIds || []).slice()
|
this.ruleForm.soft.keyStations = []
|
if (!this.ruleForm.startStationId && this.previewForm.startStationId) {
|
this.ruleForm.startStationId = this.previewForm.startStationId
|
}
|
if (!this.ruleForm.endStationId && this.previewForm.endStationId) {
|
this.ruleForm.endStationId = this.previewForm.endStationId
|
}
|
this.$message.success('已导入当前预览路径,共 ' + this.ruleForm.soft.preferredPath.length + ' 个站点')
|
},
|
expandRuleSoftPreferredPath: function () {
|
if (this.softExpandLoading) {
|
return
|
}
|
if (!this.ruleForm || !this.ruleForm.soft) {
|
return
|
}
|
var startStationId = this.toNumberSafe(this.ruleForm.startStationId) || this.toNumberSafe(this.previewForm.startStationId)
|
var endStationId = this.toNumberSafe(this.ruleForm.endStationId) || this.toNumberSafe(this.previewForm.endStationId)
|
if (startStationId == null || endStationId == null) {
|
this.$message.warning('请先为规则设置起点和终点,或先在右侧预览一条路径')
|
return
|
}
|
var keyStations = this.uniqueNumbers((this.ruleForm.soft.keyStations || []).slice())
|
var that = this
|
this.softExpandLoading = true
|
$.ajax({
|
url: baseUrl + '/basStationPathPolicy/expandSoftPath/auth',
|
method: 'POST',
|
headers: { token: localStorage.getItem('token') },
|
contentType: 'application/json',
|
data: JSON.stringify({
|
startStationId: startStationId,
|
endStationId: endStationId,
|
keyStations: keyStations
|
}),
|
success: function (res) {
|
that.softExpandLoading = false
|
if (res.code !== 200) {
|
that.$message.error('展开失败: ' + res.msg)
|
return
|
}
|
var data = res.data || {}
|
var pathStationIds = data.pathStationIds || []
|
if (!pathStationIds.length) {
|
that.$message.warning('没有生成可用的软偏好路径')
|
return
|
}
|
that.ruleForm.startStationId = startStationId
|
that.ruleForm.endStationId = endStationId
|
that.ruleForm.soft.keyStations = keyStations
|
that.ruleForm.soft.preferredPath = pathStationIds.slice()
|
that.$message.success(keyStations.length
|
? '已按关键点展开完整软偏好路径'
|
: '已按起终点生成完整软偏好路径')
|
},
|
error: function () {
|
that.softExpandLoading = false
|
that.$message.error('展开软偏好路径请求异常')
|
}
|
})
|
},
|
clearSoftPreferredPath: function () {
|
if (!this.ruleForm || !this.ruleForm.soft) {
|
return
|
}
|
this.ruleForm.soft.keyStations = []
|
this.ruleForm.soft.preferredPath = []
|
},
|
cloneRule: function (item) {
|
var copy = this.cloneRuleModel(item)
|
copy.ruleCode = item.ruleCode + '_copy'
|
copy.ruleName = item.ruleName + ' - 副本'
|
copy._originCode = null
|
this.ruleForm = copy
|
this.ruleDialogVisible = true
|
},
|
removeRule: function (item) {
|
var that = this
|
this.$confirm('确认删除规则 ' + item.ruleCode + ' 吗?', '提示', { type: 'warning' })
|
.then(function () {
|
that.rules = that.rules.filter(function (rule) {
|
return rule.ruleCode !== item.ruleCode
|
})
|
if (that.selectedRuleCode === item.ruleCode) {
|
that.selectedRuleCode = that.rules[0] ? that.rules[0].ruleCode : ''
|
}
|
})
|
.catch(function () {})
|
},
|
selectRule: function (item) {
|
this.selectedRuleCode = item.ruleCode
|
this.loadMapByRule(item)
|
},
|
previewRule: function (item) {
|
this.selectRule(item)
|
this.previewForm.startStationId = item.startStationId
|
this.previewForm.endStationId = item.endStationId
|
this.showRuleJson = false
|
this.showAllPathTags = false
|
if (item.startStationId && item.endStationId) {
|
this.loadPreview()
|
}
|
},
|
loadPreview: function () {
|
if (!this.previewForm.startStationId || !this.previewForm.endStationId) {
|
this.$message.warning('请选择起点和终点')
|
return
|
}
|
var that = this
|
this.previewLoading = true
|
$.ajax({
|
url: baseUrl + '/basStationPathPolicy/preview/auth',
|
method: 'GET',
|
headers: { token: localStorage.getItem('token') },
|
data: {
|
startStationId: this.previewForm.startStationId,
|
endStationId: this.previewForm.endStationId
|
},
|
success: function (res) {
|
that.previewLoading = false
|
if (res.code !== 200) {
|
that.$message.error('预览失败: ' + res.msg)
|
return
|
}
|
that.showRuleJson = false
|
that.showAllPathTags = false
|
that.previewResult = res.data || null
|
if (that.previewResult && that.previewResult.lev) {
|
that.activeMapLev = that.previewResult.lev
|
}
|
if (that.previewResult && that.previewResult.mapData) {
|
that.applyMapData(that.previewResult.mapData, that.previewResult.lev)
|
} else if (that.activeMapLev && that.mapContext.lev !== that.activeMapLev) {
|
that.loadMapByLev(that.activeMapLev)
|
}
|
that.$nextTick(function () {
|
that.centerOnPath()
|
})
|
if (!that.hasActualPath) {
|
that.$message.warning('当前起终点未计算到可行路径,请检查规则或楼层地图')
|
}
|
},
|
error: function () {
|
that.previewLoading = false
|
that.$message.error('预览请求异常')
|
}
|
})
|
},
|
loadMapByRule: function (rule) {
|
if (!rule) {
|
return
|
}
|
var station = this.findStation(rule.startStationId || rule.endStationId)
|
if (station && station.stationLev && this.mapContext.lev !== station.stationLev) {
|
this.loadMapByLev(station.stationLev)
|
}
|
},
|
loadMapByLev: function (lev) {
|
if (!lev) {
|
return
|
}
|
if (this.mapContext.lev === lev && this.mapContext.nodes && this.mapContext.nodes.length) {
|
return
|
}
|
var that = this
|
$.ajax({
|
url: baseUrl + '/basMap/lev/' + lev + '/auth',
|
method: 'GET',
|
headers: { token: localStorage.getItem('token') },
|
success: function (res) {
|
if (res.code !== 200) {
|
that.$message.error('加载楼层地图失败: ' + res.msg)
|
return
|
}
|
that.applyMapData(res.data, lev)
|
},
|
error: function () {
|
that.$message.error('加载楼层地图请求异常')
|
}
|
})
|
},
|
applyMapData: function (mapData, lev) {
|
var parsed = mapData
|
if (typeof mapData === 'string') {
|
try {
|
parsed = JSON.parse(mapData)
|
} catch (e) {
|
parsed = []
|
}
|
}
|
if (!Array.isArray(parsed)) {
|
this.mapContext = { lev: lev, width: 0, height: 0, nodes: [], nodeMap: {} }
|
return
|
}
|
var cellStep = 26
|
var margin = 22
|
var nodes = []
|
var nodeMap = {}
|
var rows = parsed.length
|
var cols = 0
|
for (var r = 0; r < parsed.length; r++) {
|
var row = parsed[r] || []
|
cols = Math.max(cols, row.length)
|
for (var c = 0; c < row.length; c++) {
|
var cell = row[c] || {}
|
var type = cell.type
|
var mergeType = cell.mergeType
|
if (!(type === 'devp' || (type === 'merge' && mergeType === 'devp'))) {
|
continue
|
}
|
var value = this.parseJson(cell.value)
|
var stationId = value ? value.stationId : null
|
if (!stationId) {
|
continue
|
}
|
var node = {
|
stationId: stationId,
|
row: r,
|
col: c,
|
x: margin + c * cellStep,
|
y: margin + r * cellStep,
|
stationAlias: value.stationAlias || '',
|
isLiftTransfer: value.isLiftTransfer === 1 || value.isLiftTransfer === true
|
}
|
nodes.push(node)
|
nodeMap[String(stationId)] = node
|
}
|
}
|
this.mapContext = {
|
lev: lev,
|
width: margin * 2 + Math.max(cols - 1, 0) * cellStep + 40,
|
height: margin * 2 + Math.max(rows - 1, 0) * cellStep + 40,
|
nodes: nodes,
|
nodeMap: nodeMap
|
}
|
this.fitMap()
|
},
|
fitMap: function () {
|
var wrap = this.$refs.mapCanvasWrap
|
if (!wrap || !this.mapContext.width || !this.mapContext.height) {
|
return
|
}
|
var bounds = this.getMapContentBounds()
|
var contentWidth = Math.max((bounds.maxX - bounds.minX), 1)
|
var contentHeight = Math.max((bounds.maxY - bounds.minY), 1)
|
var usableWidth = Math.max(wrap.clientWidth - 30, 320)
|
var usableHeight = Math.max(wrap.clientHeight - 30, 240)
|
var scaleX = usableWidth / contentWidth
|
var scaleY = usableHeight / contentHeight
|
var scale = Math.min(scaleX, scaleY, 1.7)
|
var zoomPercent = Math.max(60, Math.min(220, Math.floor(scale * 100)))
|
var centerX = (bounds.minX + bounds.maxX) / 2
|
var centerY = (bounds.minY + bounds.maxY) / 2
|
this.mapZoomPercent = zoomPercent
|
this.mapPanX = Math.round(wrap.clientWidth / 2 - centerX * (zoomPercent / 100))
|
this.mapPanY = Math.round(wrap.clientHeight / 2 - centerY * (zoomPercent / 100))
|
},
|
centerOnPath: function () {
|
var wrap = this.$refs.mapCanvasWrap
|
var stage = this.$refs.mapStage
|
if (!wrap || !stage || !this.actualPathPolyline) {
|
return
|
}
|
var stationIds = this.previewResult && this.previewResult.pathStationIds ? this.previewResult.pathStationIds : []
|
var points = this.resolvePoints(stationIds)
|
if (!points.length) {
|
return
|
}
|
var minX = points[0].x
|
var maxX = points[0].x
|
var minY = points[0].y
|
var maxY = points[0].y
|
points.forEach(function (point) {
|
minX = Math.min(minX, point.x)
|
maxX = Math.max(maxX, point.x)
|
minY = Math.min(minY, point.y)
|
maxY = Math.max(maxY, point.y)
|
})
|
var scale = this.mapZoomPercent / 100
|
this.mapPanX = Math.round(wrap.clientWidth / 2 - ((minX + maxX) / 2) * scale)
|
this.mapPanY = Math.round(wrap.clientHeight / 2 - ((minY + maxY) / 2) * scale)
|
},
|
resetPreview: function () {
|
this.previewForm.startStationId = null
|
this.previewForm.endStationId = null
|
this.previewResult = null
|
this.pickedStationId = null
|
this.showRuleJson = false
|
this.showAllPathTags = false
|
},
|
updateMapZoom: function (nextPercent) {
|
var wrap = this.$refs.mapCanvasWrap
|
var zoomPercent = this.toNumberSafe(nextPercent)
|
if (zoomPercent == null) {
|
return
|
}
|
zoomPercent = Math.max(60, Math.min(220, zoomPercent))
|
if (!wrap || !this.mapContext.width || !this.mapContext.height) {
|
this.mapZoomPercent = zoomPercent
|
return
|
}
|
this.setMapZoomAroundPoint(zoomPercent, wrap.clientWidth / 2, wrap.clientHeight / 2)
|
},
|
setMapZoomAroundPoint: function (nextPercent, anchorX, anchorY) {
|
var currentPercent = this.mapZoomPercent
|
if (!currentPercent || currentPercent === nextPercent) {
|
this.mapZoomPercent = nextPercent
|
return
|
}
|
var currentScale = currentPercent / 100
|
var nextScale = nextPercent / 100
|
if (!currentScale || !nextScale) {
|
this.mapZoomPercent = nextPercent
|
return
|
}
|
var mapX = (anchorX - this.mapPanX) / currentScale
|
var mapY = (anchorY - this.mapPanY) / currentScale
|
this.mapZoomPercent = nextPercent
|
this.mapPanX = Math.round(anchorX - mapX * nextScale)
|
this.mapPanY = Math.round(anchorY - mapY * nextScale)
|
},
|
handleMapWheel: function (event) {
|
if (!this.mapContext.nodes.length) {
|
return
|
}
|
if (event.ctrlKey || event.metaKey) {
|
var wrap = this.$refs.mapCanvasWrap
|
if (!wrap) {
|
return
|
}
|
var rect = wrap.getBoundingClientRect()
|
var delta = event.deltaY < 0 ? 10 : -10
|
var nextPercent = Math.max(60, Math.min(220, this.mapZoomPercent + delta))
|
this.setMapZoomAroundPoint(nextPercent, event.clientX - rect.left, event.clientY - rect.top)
|
return
|
}
|
this.mapPanX -= event.deltaX
|
this.mapPanY -= event.deltaY
|
},
|
beginMapDrag: function (event) {
|
var wrap = this.$refs.mapCanvasWrap
|
if (!wrap || !this.mapContext.nodes.length) {
|
return
|
}
|
if (event && event.button != null && event.button !== 0) {
|
return
|
}
|
this.mapDragActive = true
|
this.mapDragMoved = false
|
this.mapDragStartX = event.clientX
|
this.mapDragStartY = event.clientY
|
this.mapDragOriginPanX = this.mapPanX
|
this.mapDragOriginPanY = this.mapPanY
|
document.addEventListener('mousemove', this.handleMapDragMove)
|
document.addEventListener('mouseup', this.endMapDrag)
|
if (event.preventDefault) {
|
event.preventDefault()
|
}
|
},
|
handleMapDragMove: function (event) {
|
if (!this.mapDragActive) {
|
return
|
}
|
var wrap = this.$refs.mapCanvasWrap
|
if (!wrap) {
|
this.endMapDrag()
|
return
|
}
|
var deltaX = event.clientX - this.mapDragStartX
|
var deltaY = event.clientY - this.mapDragStartY
|
if (Math.abs(deltaX) > 3 || Math.abs(deltaY) > 3) {
|
this.mapDragMoved = true
|
}
|
this.mapPanX = this.mapDragOriginPanX + deltaX
|
this.mapPanY = this.mapDragOriginPanY + deltaY
|
},
|
endMapDrag: function () {
|
if (!this.mapDragActive) {
|
this.detachMapDragListeners()
|
return
|
}
|
this.mapDragActive = false
|
this.detachMapDragListeners()
|
if (this.mapDragMoved) {
|
this.suppressNodeClick = true
|
var that = this
|
window.setTimeout(function () {
|
that.suppressNodeClick = false
|
}, 0)
|
}
|
},
|
detachMapDragListeners: function () {
|
document.removeEventListener('mousemove', this.handleMapDragMove)
|
document.removeEventListener('mouseup', this.endMapDrag)
|
},
|
pickNode: function (node) {
|
if (this.suppressNodeClick) {
|
return
|
}
|
this.pickedStationId = node.stationId
|
},
|
applyPickedStation: function (field) {
|
if (!this.pickedStationId) {
|
return
|
}
|
if (field === 'start') {
|
this.previewForm.startStationId = this.pickedStationId
|
} else if (field === 'end') {
|
this.previewForm.endStationId = this.pickedStationId
|
} else if (field === 'ruleStart' && this.ruleForm) {
|
this.ruleForm.startStationId = this.pickedStationId
|
} else if (field === 'ruleEnd' && this.ruleForm) {
|
this.ruleForm.endStationId = this.pickedStationId
|
} else if (field === 'mustPass' && this.ruleForm) {
|
this.pushUniqueStation(this.ruleForm.hard.mustPassStations, this.pickedStationId)
|
} else if (field === 'forbid' && this.ruleForm) {
|
this.pushUniqueStation(this.ruleForm.hard.forbidStations, this.pickedStationId)
|
} else if (field === 'waypoint' && this.ruleForm) {
|
this.pushUniqueStation(this.ruleForm.waypoint.stations, this.pickedStationId)
|
} else if (field === 'preferred' && this.ruleForm) {
|
this.pushUniqueStation(this.ruleForm.soft.preferredPath, this.pickedStationId)
|
}
|
},
|
pushUniqueStation: function (list, stationId) {
|
if (!Array.isArray(list)) {
|
return
|
}
|
var value = this.toNumberSafe(stationId)
|
if (value == null) {
|
return
|
}
|
var exists = list.some(function (item) {
|
return String(item) === String(value)
|
})
|
if (!exists) {
|
list.push(value)
|
}
|
},
|
moveListItem: function (list, index, offset) {
|
if (!Array.isArray(list)) {
|
return
|
}
|
var targetIndex = index + offset
|
if (index < 0 || targetIndex < 0 || index >= list.length || targetIndex >= list.length) {
|
return
|
}
|
var moved = list.splice(index, 1)[0]
|
list.splice(targetIndex, 0, moved)
|
},
|
removeListItem: function (list, index) {
|
if (!Array.isArray(list) || index < 0 || index >= list.length) {
|
return
|
}
|
list.splice(index, 1)
|
},
|
buildLookup: function (list) {
|
var lookup = {}
|
;(list || []).forEach(function (item) {
|
if (item != null && item !== '') {
|
lookup[String(item)] = true
|
}
|
})
|
return lookup
|
},
|
nodeStyle: function (node) {
|
return {
|
left: node.x + 'px',
|
top: node.y + 'px'
|
}
|
},
|
nodeClasses: function (node) {
|
var stationId = node.stationId
|
var classes = []
|
if (String(this.pickedStationId || '') === String(stationId)) {
|
classes.push('is-picked')
|
}
|
if (this.previewForm.startStationId === stationId) {
|
classes.push('is-start')
|
}
|
if (this.previewForm.endStationId === stationId) {
|
classes.push('is-end')
|
}
|
if (this.pathStationSet()[stationId]) {
|
classes.push('is-path')
|
}
|
if (this.preferredStationSet()[stationId]) {
|
classes.push('is-preferred')
|
}
|
if (this.waypointStationSet()[stationId]) {
|
classes.push('is-waypoint')
|
}
|
if (this.forbidStationSet()[stationId]) {
|
classes.push('is-forbid')
|
}
|
if (this.mustPassStationSet()[stationId]) {
|
classes.push('is-must-pass')
|
}
|
return classes
|
},
|
showNodeLabel: function (node) {
|
var stationId = node.stationId
|
return !!(
|
this.previewForm.startStationId === stationId
|
|| this.previewForm.endStationId === stationId
|
|| this.pathStationSet()[stationId]
|
|| this.waypointStationSet()[stationId]
|
|| this.forbidStationSet()[stationId]
|
|| this.mustPassStationSet()[stationId]
|
|| String(this.pickedStationId || '') === String(stationId)
|
)
|
},
|
stationNodeTitle: function (node) {
|
return this.stationLabel(node.stationId)
|
},
|
stationOptionLabel: function (station) {
|
if (!station) {
|
return ''
|
}
|
var alias = station.stationAlias ? ' · ' + station.stationAlias : ''
|
return 'L' + (station.stationLev || '-') + ' · ' + station.stationId + alias
|
},
|
stationLabel: function (stationId) {
|
var station = this.findStation(stationId)
|
return station ? this.stationOptionLabel(station) : String(stationId || '')
|
},
|
findStation: function (stationId) {
|
if (stationId == null) {
|
return null
|
}
|
return this.stationMapById[String(stationId)] || null
|
},
|
routeLabel: function (rule) {
|
if (!rule.startStationId && !rule.endStationId) {
|
return '通配规则'
|
}
|
return (rule.startStationId || '*') + ' → ' + (rule.endStationId || '*')
|
},
|
ruleSummaryCount: function (hard) {
|
hard = hard || {}
|
return (hard.mustPassStations || []).length
|
+ (hard.forbidStations || []).length
|
+ (hard.mustPassEdges || []).length
|
+ (hard.forbidEdges || []).length
|
},
|
pathStationSet: function () {
|
return this.pathStationLookup
|
},
|
preferredStationSet: function () {
|
return this.preferredStationLookup
|
},
|
waypointStationSet: function () {
|
return this.waypointStationLookup
|
},
|
forbidStationSet: function () {
|
return this.forbidStationLookup
|
},
|
mustPassStationSet: function () {
|
return this.mustPassStationLookup
|
},
|
buildPolyline: function (stationIds) {
|
var points = this.resolvePoints(stationIds)
|
if (!points.length) {
|
return ''
|
}
|
return points.map(function (point) {
|
return point.x + ',' + point.y
|
}).join(' ')
|
},
|
resolvePoints: function (stationIds) {
|
var points = []
|
var map = this.mapContext.nodeMap || {}
|
;(stationIds || []).forEach(function (stationId) {
|
var node = map[String(stationId)]
|
if (node) {
|
points.push({ x: node.x, y: node.y })
|
}
|
})
|
return points
|
},
|
getMapContentBounds: function () {
|
var nodes = this.mapContext && this.mapContext.nodes ? this.mapContext.nodes : []
|
if (!nodes.length) {
|
return {
|
minX: 0,
|
maxX: this.mapContext.width || 0,
|
minY: 0,
|
maxY: this.mapContext.height || 0
|
}
|
}
|
var minX = null
|
var maxX = null
|
var minY = null
|
var maxY = null
|
nodes.forEach(function (node) {
|
if (!node) {
|
return
|
}
|
minX = minX == null ? node.x : Math.min(minX, node.x)
|
maxX = maxX == null ? node.x : Math.max(maxX, node.x)
|
minY = minY == null ? node.y : Math.min(minY, node.y)
|
maxY = maxY == null ? node.y : Math.max(maxY, node.y)
|
})
|
var padding = 48
|
return {
|
minX: Math.max((minX == null ? 0 : minX) - padding, 0),
|
maxX: Math.min((maxX == null ? 0 : maxX) + padding, this.mapContext.width || Number.MAX_SAFE_INTEGER),
|
minY: Math.max((minY == null ? 0 : minY) - padding, 0),
|
maxY: Math.min((maxY == null ? 0 : maxY) + padding, this.mapContext.height || Number.MAX_SAFE_INTEGER)
|
}
|
},
|
defaultProfileConfig: function () {
|
return createDefaultProfileConfig()
|
},
|
defaultProfile: function () {
|
return createDefaultProfile()
|
},
|
defaultRule: function () {
|
return createDefaultRule(this.defaultProfileCode || 'default')
|
},
|
normalizeProfile: function (raw) {
|
var config = Object.assign({}, this.defaultProfileConfig(), this.parseJson(raw.configJson) || raw.config || {})
|
return {
|
id: raw.id || null,
|
profileCode: raw.profileCode || '',
|
profileName: raw.profileName || raw.profileCode || '',
|
priority: raw.priority == null ? 100 : Number(raw.priority),
|
status: raw.status == null ? 1 : Number(raw.status),
|
memo: raw.memo || '',
|
config: config
|
}
|
},
|
normalizeRule: function (raw) {
|
var rule = this.defaultRule()
|
var hard = Object.assign({}, rule.hard, this.parseJson(raw.hardJson) || raw.hard || {})
|
var waypoint = Object.assign({}, rule.waypoint, this.parseJson(raw.waypointJson) || raw.waypoint || {})
|
var soft = Object.assign({}, rule.soft, this.parseJson(raw.softJson) || raw.soft || {})
|
var fallback = Object.assign({}, rule.fallback, this.parseJson(raw.fallbackJson) || raw.fallback || {})
|
hard.mustPassEdgesText = (hard.mustPassEdges || []).join('\n')
|
hard.forbidEdgesText = (hard.forbidEdges || []).join('\n')
|
rule.id = raw.id || null
|
rule.ruleCode = raw.ruleCode || ''
|
rule.ruleName = raw.ruleName || raw.ruleCode || ''
|
rule.priority = raw.priority == null ? 100 : Number(raw.priority)
|
rule.status = raw.status == null ? 1 : Number(raw.status)
|
rule.sceneType = raw.sceneType || 'station'
|
rule.startStationId = raw.startStationId == null ? null : Number(raw.startStationId)
|
rule.endStationId = raw.endStationId == null ? null : Number(raw.endStationId)
|
rule.profileCode = raw.profileCode || ''
|
rule.memo = raw.memo || ''
|
rule.hard = hard
|
rule.waypoint = waypoint
|
rule.soft = soft
|
rule.fallback = fallback
|
return rule
|
},
|
cloneProfileModel: function (item) {
|
var model = this.normalizeProfile(item)
|
model._originCode = item.profileCode || item._originCode || null
|
return JSON.parse(JSON.stringify(model))
|
},
|
cloneRuleModel: function (item) {
|
var model = this.normalizeRule(item)
|
model._originCode = item.ruleCode || item._originCode || null
|
return JSON.parse(JSON.stringify(model))
|
},
|
sanitizeProfileForSave: function (item) {
|
return {
|
id: item.id || null,
|
profileCode: item.profileCode,
|
profileName: item.profileName,
|
priority: Number(item.priority || 100),
|
status: Number(item.status || 0),
|
memo: item.memo || '',
|
config: Object.assign({}, item.config || {})
|
}
|
},
|
sanitizeRuleForSave: function (item) {
|
var hard = Object.assign({}, item.hard || {})
|
hard.mustPassEdges = this.parseLines(hard.mustPassEdgesText || hard.mustPassEdges || [])
|
hard.forbidEdges = this.parseLines(hard.forbidEdgesText || hard.forbidEdges || [])
|
delete hard.mustPassEdgesText
|
delete hard.forbidEdgesText
|
return {
|
id: item.id || null,
|
ruleCode: item.ruleCode,
|
ruleName: item.ruleName,
|
priority: Number(item.priority || 100),
|
status: Number(item.status || 0),
|
sceneType: item.sceneType || '',
|
startStationId: item.startStationId == null ? null : Number(item.startStationId),
|
endStationId: item.endStationId == null ? null : Number(item.endStationId),
|
profileCode: item.profileCode || '',
|
memo: item.memo || '',
|
hard: {
|
mustPassStations: this.uniqueNumbers(hard.mustPassStations || []),
|
forbidStations: this.uniqueNumbers(hard.forbidStations || []),
|
mustPassEdges: hard.mustPassEdges,
|
forbidEdges: hard.forbidEdges
|
},
|
waypoint: {
|
stations: this.uniqueNumbers((item.waypoint && item.waypoint.stations) || [])
|
},
|
soft: {
|
keyStations: this.uniqueNumbers((item.soft && item.soft.keyStations) || []),
|
preferredPath: this.uniqueNumbers((item.soft && item.soft.preferredPath) || []),
|
deviationWeight: this.toNumberSafe(item.soft && item.soft.deviationWeight) || 0,
|
maxOffPathCount: this.toNumberSafe(item.soft && item.soft.maxOffPathCount) || 0
|
},
|
fallback: {
|
strictWaypoint: !!(item.fallback && item.fallback.strictWaypoint),
|
allowSoftDegrade: !(item.fallback && item.fallback.allowSoftDegrade === false)
|
}
|
}
|
},
|
findProfileIndex: function (profileCode) {
|
for (var i = 0; i < this.profiles.length; i++) {
|
if (this.profiles[i].profileCode === profileCode) {
|
return i
|
}
|
}
|
return -1
|
},
|
findRuleIndex: function (ruleCode) {
|
for (var i = 0; i < this.rules.length; i++) {
|
if (this.rules[i].ruleCode === ruleCode) {
|
return i
|
}
|
}
|
return -1
|
},
|
parseJson: function (value) {
|
if (!value) {
|
return null
|
}
|
if (typeof value === 'object') {
|
return value
|
}
|
try {
|
return JSON.parse(value)
|
} catch (e) {
|
return null
|
}
|
},
|
parseLines: function (value) {
|
if (Array.isArray(value)) {
|
return value.filter(function (item) { return !!String(item || '').trim() })
|
}
|
return String(value || '')
|
.split('\n')
|
.map(function (item) { return item.trim() })
|
.filter(function (item) { return !!item })
|
},
|
toNumberSafe: function (value) {
|
if (value == null || value === '') {
|
return null
|
}
|
var num = Number(value)
|
return isNaN(num) ? null : num
|
},
|
notNull: function (value) {
|
return value != null
|
},
|
uniqueNumbers: function (list) {
|
var result = []
|
var seen = {}
|
;(list || []).forEach(function (item) {
|
var num = this.toNumberSafe(item)
|
if (num == null) {
|
return
|
}
|
if (!seen[String(num)]) {
|
seen[String(num)] = true
|
result.push(num)
|
}
|
}.bind(this))
|
return result
|
},
|
isBlank: function (text) {
|
return text == null || String(text).trim() === ''
|
}
|
}
|
})
|