console
var data = { "startPileStr": "K000+000", "startPileNo": "0", "endPileStr": "K5+000", "endPileNo": "5000", "tunnelIntroduction": "1.比例缩放:鼠标在画布内时支持 Ctrl + 滚轮 缩放,Ctrl + alt + 滚轮 快速缩放; 2.横向滚动:鼠标在画布内时支持 shift + 滚轮 横向滚动,shift + alt + 滚轮 快速横向滚动 3.画布拖拽: 鼠标在画布内可拖拽画布 4.下载支持:根据当前缩放比例下载,最大支持65000px 宽度画布; 5.宽高自适应:切换宽高自动重新绘制画布内容", "designInfoList": [{ "id": "4f9dfeaa-727d-11eb-9fe2-0242ac110006", "tunnelId": "92627b5b5b4240acb67c0078f2b312da", "paramTunnelTrunk": "353e9b1ec6b846baad0296434c73e4b6", "tunnelTrunkName": "单洞", "startPileNo": "0", "endPileNo": "1100", "startPileNoStr": "K000+000", "endPileNoStr": "K1+100", "levelId": "fad026e8e09741d987c4b7346e3783d4", "levelName": "Ⅰ级", "levelCode": "surrounding_rock_classification-Ⅰ", "levelType": "0", "remark": "" }, { "id": "4f9e0095-727d-11eb-9fe2-0242ac110006", "tunnelId": "92627b5b5b4240acb67c0078f2b312da", "paramTunnelTrunk": "353e9b1ec6b846baad0296434c73e4b6", "tunnelTrunkName": "单洞", "startPileNo": "1100", "endPileNo": "2200", "startPileNoStr": "K1+100", "endPileNoStr": "K2+200", "levelId": "96f81ff4bd504b17a630b266147bad06", "levelName": "Ⅲ级", "levelCode": "surrounding_rock_classification-Ⅲ", "levelType": "0", "remark": "" }, { "id": "4f9e0120-727d-11eb-9fe2-0242ac110006", "tunnelId": "92627b5b5b4240acb67c0078f2b312da", "paramTunnelTrunk": "353e9b1ec6b846baad0296434c73e4b6", "tunnelTrunkName": "单洞", "startPileNo": "2200", "endPileNo": "3300", "startPileNoStr": "K2+200", "endPileNoStr": "K3+300", "levelId": "820aed7377274d1e9aa09f6c57eb5524", "levelName": "Ⅴ级", "levelCode": "surrounding_rock_classification-Ⅴ", "levelType": "0", "remark": "" }, { "id": "4f9e01a3-727d-11eb-9fe2-0242ac110006", "tunnelId": "92627b5b5b4240acb67c0078f2b312da", "paramTunnelTrunk": "353e9b1ec6b846baad0296434c73e4b6", "tunnelTrunkName": "单洞", "startPileNo": "3300", "endPileNo": "4400", "startPileNoStr": "K3+300", "endPileNoStr": "K4+400", "levelId": "f4e852ba0c414f25855ab961eccc97cc", "levelName": "Ⅱ级", "levelCode": "surrounding_rock_classification-Ⅱ", "levelType": "0", "remark": "" }, { "id": "4f9e0213-727d-11eb-9fe2-0242ac110006", "tunnelId": "92627b5b5b4240acb67c0078f2b312da", "paramTunnelTrunk": "353e9b1ec6b846baad0296434c73e4b6", "tunnelTrunkName": "单洞", "startPileNo": "4400", "endPileNo": "4600", "startPileNoStr": "K4+400", "endPileNoStr": "K4+600", "levelId": "10b2f3b220ab44acbef990aa6945697e", "levelName": "Ⅳ级", "levelCode": "surrounding_rock_classification-Ⅳ", "levelType": "0", "remark": "" }, { "id": "4f9e028f-727d-11eb-9fe2-0242ac110006", "tunnelId": "92627b5b5b4240acb67c0078f2b312da", "paramTunnelTrunk": "353e9b1ec6b846baad0296434c73e4b6", "tunnelTrunkName": "单洞", "startPileNo": "4600", "endPileNo": "5000", "startPileNoStr": "K4+600", "endPileNoStr": "K5+000", "levelId": "53bc75c179a141179ce9da05913544df", "levelName": "Ⅵ级", "levelCode": "surrounding_rock_classification-Ⅵ", "levelType": "0", "remark": "" }], "slopeAndShaftList": [], "constructionProgress": null, "qualityInspection": null, "processIma": [{ "name": "明洞及洞门", "progressPercent": null, "subList": [{ "name": "明洞回填", "progressPercent": null, "subList": null, "dataList": [{ "id": "a4928b7c-84a7-43a9-83ec-dc91ede81960", "name": null, "startPileStr": "K0+080", "startPileNo": "80", "endPileStr": "K0+090", "endPileNo": "90", "progressPercent": null, "scoreLevel": "0" }] }, { "name": "挡墙砌筑", "progressPercent": null, "subList": null, "dataList": [{ "id": "efa81292-3982-48a2-8512-589a714f813a", "name": null, "startPileStr": "K0+060", "startPileNo": "60", "endPileStr": "K0+080", "endPileNo": "80", "progressPercent": null, "scoreLevel": "2" }, { "id": "8e15aa7e-641b-4ebf-ba57-5dfc4443a11a", "name": null, "startPileStr": "K0+050", "startPileNo": "50", "endPileStr": "K0+055", "endPileNo": "55", "progressPercent": null, "scoreLevel": "0" }, { "id": "c7b0a35f-953a-4c69-adb2-8a80fe46601d", "name": null, "startPileStr": "K4+050", "startPileNo": "4050", "endPileStr": "K4+100", "endPileNo": "4100", "progressPercent": null, "scoreLevel": "3" }] }, { "name": "明洞排水", "progressPercent": null, "subList": null, "dataList": [{ "id": "b69cd7e2-bfca-4d34-93c8-816a762bc57b", "name": null, "startPileStr": "K0+070", "startPileNo": "70", "endPileStr": "K0+080", "endPileNo": "80", "progressPercent": null, "scoreLevel": "4" }] }, { "name": "明洞钢筋", "progressPercent": null, "subList": null, "dataList": [{ "id": "0f40f1fb-d2ce-47d8-b4d0-7d43cd6d4853", "name": null, "startPileStr": "K0+120", "startPileNo": "120", "endPileStr": "K0+150", "endPileNo": "150", "progressPercent": null, "scoreLevel": "5" }, { "id": "8bd22b76-a1f0-4ae6-a057-e88f490393ae", "name": null, "startPileStr": "K0+100", "startPileNo": "100", "endPileStr": "K0+130", "endPileNo": "130", "progressPercent": null, "scoreLevel": "4" }, { "id": "52e58e95-5482-446c-9ea0-e9b321123253", "name": null, "startPileStr": "K0+050", "startPileNo": "50", "endPileStr": "K0+060", "endPileNo": "60", "progressPercent": null, "scoreLevel": "2" }] }, { "name": "基础", "progressPercent": null, "subList": null, "dataList": [{ "id": "7642d7fe-a424-4506-8c17-873f2eab253a", "name": null, "startPileStr": "K0+050", "startPileNo": "50", "endPileStr": "K0+055", "endPileNo": "55", "progressPercent": null, "scoreLevel": "1" }, { "id": "315ff61c-5ea2-4cdb-8cb4-eb87765e4950", "name": null, "startPileStr": "K0+045", "startPileNo": "45", "endPileStr": "K0+050", "endPileNo": "50", "progressPercent": null, "scoreLevel": "5" }, { "id": "3d1d40b9-89f5-4dff-b532-8b54033b31b9", "name": null, "startPileStr": "K0+030", "startPileNo": "30", "endPileStr": "K0+035", "endPileNo": "35", "progressPercent": null, "scoreLevel": "4" }, { "id": "42db56ef-f0bd-4dd4-a82a-7abefac3f6ef", "name": null, "startPileStr": "K0+010", "startPileNo": "10", "endPileStr": "K0+020", "endPileNo": "20", "progressPercent": null, "scoreLevel": "4" }, { "id": "2ddc5007-31c9-4715-90ef-7f06a409b951", "name": null, "startPileStr": "K0+000", "startPileNo": "0", "endPileStr": "K0+010", "endPileNo": "10", "progressPercent": null, "scoreLevel": "4" }, { "id": "132d486f-86ce-452c-9a35-aaf199cd57b8", "name": null, "startPileStr": "K0+020", "startPileNo": "20", "endPileStr": "K0+050", "endPileNo": "50", "progressPercent": null, "scoreLevel": "5" }, { "id": "8f7e12a1-ec2f-41e1-bf62-9fa64ce16d23", "name": null, "startPileStr": "K4+250", "startPileNo": "4250", "endPileStr": "K4+250", "endPileNo": "4250", "progressPercent": null, "scoreLevel": "4" }, { "id": "db6ba73d-1bae-4753-901b-23aa61f6444a", "name": null, "startPileStr": "K4+415", "startPileNo": "4415", "endPileStr": "K4+250", "endPileNo": "4250", "progressPercent": null, "scoreLevel": "5" }, { "id": "23a138fb-53fa-4dd1-b93a-0cacf2975223", "name": null, "startPileStr": "K4+300", "startPileNo": "4300", "endPileStr": "K4+400", "endPileNo": "4400", "progressPercent": null, "scoreLevel": "2" }, { "id": "3d5e1179-a29c-4c0e-bb95-b169eb2dbbf0", "name": null, "startPileStr": "K4+100", "startPileNo": "4100", "endPileStr": "K4+200", "endPileNo": "4200", "progressPercent": null, "scoreLevel": "5" }, { "id": "a9db3c00-4284-4d87-a036-a96a2148cb25", "name": null, "startPileStr": "K53+100", "startPileNo": "53100", "endPileStr": "K55+200", "endPileNo": "55200", "progressPercent": null, "scoreLevel": "0" }] }, { "name": "明洞衬砌", "progressPercent": null, "subList": null, "dataList": [{ "id": "6a6d815b-d2c6-4d7e-bc22-91b773cc54dd", "name": null, "startPileStr": "K0+090", "startPileNo": "90", "endPileStr": "K0+110", "endPileNo": "110", "progressPercent": null, "scoreLevel": "4" }, { "id": "3b296752-9f9f-42ea-ae9b-d4570133912a", "name": null, "startPileStr": "K0+060", "startPileNo": "60", "endPileStr": "K0+070", "endPileNo": "70", "progressPercent": null, "scoreLevel": "3" }, { "id": "46537ffc-6755-45c3-a87a-696778b7ae50", "name": null, "startPileStr": "K0+020", "startPileNo": "20", "endPileStr": "K0+030", "endPileNo": "30", "progressPercent": null, "scoreLevel": "4" }] }], "dataList": null }, { "name": "仰拱及铺底", "progressPercent": null, "subList": [{ "name": "仰拱防水层铺设", "progressPercent": null, "subList": null, "dataList": [{ "id": "3c9570e6-791e-4c46-bb2b-1072c242d218", "name": null, "startPileStr": "K0+035", "startPileNo": "35", "endPileStr": "K0+045", "endPileNo": "45", "progressPercent": null, "scoreLevel": "3" }] }, { "name": "仰拱初支钢筋网", "progressPercent": null, "subList": null, "dataList": [{ "id": "6898ff39-7813-4c3a-9f26-32aa1e856cd6", "name": null, "startPileStr": "K0+020", "startPileNo": "20", "endPileStr": "K0+030", "endPileNo": "30", "progressPercent": null, "scoreLevel": "2" }] }, { "name": "开挖", "progressPercent": null, "subList": null, "dataList": [{ "id": "c0c20b71-cf60-4597-8241-2c113c776ac1", "name": null, "startPileStr": "K0+003", "startPileNo": "3", "endPileStr": "K0+005", "endPileNo": "5", "progressPercent": null, "scoreLevel": "5" }, { "id": "8c7db21c-54f7-40f8-af75-3f3dbd708d94", "name": null, "startPileStr": "K4+200", "startPileNo": "4200", "endPileStr": "K4+300", "endPileNo": "4300", "progressPercent": null, "scoreLevel": "4" }] }], "dataList": null }, { "name": "初期支护", "progressPercent": null, "subList": [{ "name": "初喷", "progressPercent": null, "subList": null, "dataList": [{ "id": "016f1190-6974-46df-ab12-db1f074694e2", "name": null, "startPileStr": "K0+080", "startPileNo": "80", "endPileStr": "K0+085", "endPileNo": "85", "progressPercent": null, "scoreLevel": "0" }, { "id": "12ad55ab-6e86-459e-b60d-51f67ca5990a", "name": null, "startPileStr": "K4+450", "startPileNo": "4450", "endPileStr": "K4+500", "endPileNo": "4500", "progressPercent": null, "scoreLevel": "4" }, { "id": "dd8a7f7e-b95d-4678-a7c5-bf0335be3699", "name": null, "startPileStr": "K4+200", "startPileNo": "4200", "endPileStr": "K4+200", "endPileNo": "4200", "progressPercent": null, "scoreLevel": "3" }] }, { "name": "钢筋网", "progressPercent": null, "subList": null, "dataList": [{ "id": "09d8faa2-1ad3-4c03-aa9d-ea572227fa38", "name": null, "startPileStr": "K0+100", "startPileNo": "100", "endPileStr": "K0+110", "endPileNo": "110", "progressPercent": null, "scoreLevel": "1" }] }], "dataList": null }, { "name": "电缆槽、中心排水沟", "progressPercent": null, "subList": [{ "name": "钢筋", "progressPercent": null, "subList": null, "dataList": [{ "id": "65bd2a67-ea01-4fa5-8e17-f11d8ba082f9", "name": null, "startPileStr": "K0+055", "startPileNo": "55", "endPileStr": "K0+075", "endPileNo": "75", "progressPercent": null, "scoreLevel": "4" }, { "id": "db3f29fe-f22f-468d-8175-b5e55a347abd", "name": null, "startPileStr": "K0+002", "startPileNo": "2", "endPileStr": "K0+005", "endPileNo": "5", "progressPercent": null, "scoreLevel": "2" }] }, { "name": "开挖", "progressPercent": null, "subList": null, "dataList": [{ "id": "eb2fba6c-4fca-45d8-8298-ec3785fca436", "name": null, "startPileStr": "K0+055", "startPileNo": "55", "endPileStr": "K0+060", "endPileNo": "60", "progressPercent": null, "scoreLevel": "0" }, { "id": "e8f3cebb-946d-411c-8a70-df58ae1d6589", "name": null, "startPileStr": "K0+050", "startPileNo": "50", "endPileStr": "K0+055", "endPileNo": "55", "progressPercent": null, "scoreLevel": "0" }, { "id": "c4d13f3d-7f24-47c8-bca6-1ac8d21c625c", "name": null, "startPileStr": "K4+700", "startPileNo": "4700", "endPileStr": "K4+800", "endPileNo": "4800", "progressPercent": null, "scoreLevel": "4" }] }], "dataList": null }, { "name": "防寒泄水洞出水口", "progressPercent": null, "subList": [{ "name": "分层铺筑过程", "progressPercent": null, "subList": null, "dataList": [{ "id": "feb7f696-1292-43cf-a00e-aaa524ef19c0", "name": null, "startPileStr": "K0+085", "startPileNo": "85", "endPileStr": "K0+089", "endPileNo": "89", "progressPercent": null, "scoreLevel": "4" }] }], "dataList": null }, { "name": "拱墙防排水", "progressPercent": null, "subList": [{ "name": "防水层铺设前初支状况", "progressPercent": null, "subList": null, "dataList": [{ "id": "d5f220d6-43b6-4ca1-b0cb-692da371cb1d", "name": null, "startPileStr": "K0+000", "startPileNo": "0", "endPileStr": "K0+010", "endPileNo": "10", "progressPercent": null, "scoreLevel": "0" }, { "id": "742e29f2-bbb4-4be7-b76b-fc2d3138d946", "name": null, "startPileStr": "K4+300", "startPileNo": "4300", "endPileStr": "K4+350", "endPileNo": "4350", "progressPercent": null, "scoreLevel": "3" }] }, { "name": "排水管", "progressPercent": null, "subList": null, "dataList": [{ "id": "ee47c454-1d74-4201-ba12-d1e803b2d6f0", "name": null, "startPileStr": "K0+010", "startPileNo": "10", "endPileStr": "K0+050", "endPileNo": "50", "progressPercent": null, "scoreLevel": "5" }] }, { "name": "预留洞室防水层铺设前初支状况", "progressPercent": null, "subList": null, "dataList": [{ "id": "2808a70b-c56e-4418-85cf-17de67a37589", "name": null, "startPileStr": "K0+030", "startPileNo": "30", "endPileStr": "K0+030", "endPileNo": "30", "progressPercent": null, "scoreLevel": "5" }] }], "dataList": null }, { "name": "外置保温层", "progressPercent": null, "subList": [{ "name": "保温层", "progressPercent": null, "subList": null, "dataList": [{ "id": "27960d36-0a36-435d-ad9b-2249b0240fbc", "name": null, "startPileStr": "K0+010", "startPileNo": "10", "endPileStr": "K0+030", "endPileNo": "30", "progressPercent": null, "scoreLevel": "5" }, { "id": "e37196d3-5430-427c-8352-1ac79938ead6", "name": null, "startPileStr": "K0+004", "startPileNo": "4", "endPileStr": "K0+004", "endPileNo": "4", "progressPercent": null, "scoreLevel": "3" }] }], "dataList": null }, { "name": "二衬", "progressPercent": null, "subList": [{ "name": "衬砌钢筋", "progressPercent": null, "subList": null, "dataList": [{ "id": "a7fca37d-a96b-4bb4-80c6-34d42dad3744", "name": null, "startPileStr": "K4+550", "startPileNo": "4550", "endPileStr": "K4+600", "endPileNo": "4600", "progressPercent": null, "scoreLevel": "5" }] }, { "name": "内置保温层", "progressPercent": null, "subList": null, "dataList": [{ "id": "3ca2dd7e-c990-4cf1-b0fe-524656d0ad87", "name": null, "startPileStr": "K0+004", "startPileNo": "4", "endPileStr": "K0+009", "endPileNo": "9", "progressPercent": null, "scoreLevel": "1" }] }], "dataList": null }, { "name": "开挖", "progressPercent": null, "subList": [{ "name": "掌子面围岩", "progressPercent": null, "subList": null, "dataList": [{ "id": "ef077ad8-3c9f-472b-8d15-e256fbe5ae76", "name": null, "startPileStr": "K0+110", "startPileNo": "110", "endPileStr": null, "endPileNo": null, "progressPercent": null, "scoreLevel": "5" }, { "id": "bc0f6638-3682-4021-87e5-1766c62e6bba", "name": null, "startPileStr": "K0+090", "startPileNo": "90", "endPileStr": null, "endPileNo": null, "progressPercent": null, "scoreLevel": "4" }, { "id": "d54c4a90-d92c-41c3-85aa-addc7658a857", "name": null, "startPileStr": "K0+070", "startPileNo": "70", "endPileStr": null, "endPileNo": null, "progressPercent": null, "scoreLevel": "1" }, { "id": "3617bc1c-1e4b-4ef0-aa09-4ab90930cf5e", "name": null, "startPileStr": "K0+030", "startPileNo": "30", "endPileStr": null, "endPileNo": null, "progressPercent": null, "scoreLevel": "4" }, { "id": "9b77c577-7d94-4b60-ad4d-aaa179f5b42b", "name": null, "startPileStr": "K4+200", "startPileNo": "4200", "endPileStr": "K4+200", "endPileNo": "4200", "progressPercent": null, "scoreLevel": "4" }] }], "dataList": null }] }
var quinticOut = k => {
return --k * k * k * k * k + 1
}
var quadraticOut = (k) => {
return k * (2 - k);
}
new Vue({
el: '#app',
data() {
return {
isHeaderExpand: false,
tunnelName: '',
tunnelIntroduction: '',
descriptionWrapWidth: 0,
descriptionWidth: 0,
tooltipStyle: {},
lineStyle: {},
filterData: {
id: '',
projectId: '',
tunnelId: '',
tunnelTrunkId: '',
k: 0,
v: 0
},
projectOptions: [],
tunnelOptions: [],
tunnelTrunkOptions: [],
originData: {},
modalInfo: {
modal: false,
title: '详情',
id: null
},
canvasWrap: null,
tableMenu: null,
tableRule: null,
chart: null,
ctx: null,
ctxRule: null,
ctxFixed: null,
ctxMenu: null,
position: {},
wrapWidth: 0,
wrapHeight: 0,
canvasHeight: 0,
translateX: 0,
translateY: 0,
rowHeight: 40,
rowHegihtOptions: [40, 45, 50, 55, 60, 65],
colw: 40,
colWidth: 200,
lineWidth: 1,
fontSize: 16,
fontFamily: 'Microsoft YaHei Regular',
tableBgColor: 'transparent',
tableHoverBgColor: 'rgba(45, 140, 240, .1)',
tableLevel3BgColor: 'rgba(0, 0, 0, .04)',
innerWrapBgColor: '#EEF6FD',
lineColor: '#DBE2E8',
fillStyle: 'transparent',
tickTextColor: '#84A6C3',
pileStrColor: '#58718A',
inOutIconBgColor: '#90A5BA',
inOutIconColor: '#ffffff',
fontColor: '#000000',
tickFontSize: 14,
tickLength: 8,
offset: 0.5,
scrollStep: 20,
ruleStep: 400,
preMeterToPx: 4,
meterToPx: 4,
stepPx: 0.5,
meterToPxMin: 1,
meterToPxMax: 30,
ruleStepVal: 100,
faceWidth: 8,
zeroWidth: 8,
romeNumber: ['Ⅰ', 'Ⅱ', 'Ⅲ', 'Ⅳ', 'Ⅴ', 'Ⅵ', 'Ⅶ', 'Ⅷ', 'Ⅸ', 'Ⅹ'],
levelColor: ['#4ECB73', '#2DB7F5', '#FAD337', '#FF9900', '#F86F00', '#ED3F14'],
rectColor: ['#C5C9D0', '#FF582F', '#FF8F34', '#FDC500', '#3D87FF', '#29CC95'],
rectHoverColor: ['#cccccc', '#FC7858', '#FAAE70', '#F7D965', '#84B3FE', '#64D9B4'],
minStripe: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGAgMAAACdogfbAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAJUExURf///////////45K5f4AAAADdFJOUwQ/Ta3MexMAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAaSURBVAjXY2BzYJBkYEhhYJggwOCQwMA4AQASZAKV6dxVvQAAAABJRU5ErkJggg==',
maxStripe: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAhAQMAAACC6DsSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAGUExURf///0dwTHBCPHYAAAACdFJOUx4AotLy5wAAAAlwSFlzAAAASAAAAEgARslrPgAAAFpJREFUCNeNzLENwCAUA9H7SkHJCBklo8FojMIIlCkQjokyQCTrqiejm4QGGXVO1LhQpSC8RYjJIczE2DETbady7cj0TWhi6yTfkn1r1jFrUHz7sfWHYRaYxQM150HTObrW+AAAAABJRU5ErkJggg==',
minStripeColor: null,
maxStripeColor: null,
scale: 1,
targetX: 0,
targetY: 0,
animationStepX: 0,
animationStepY: 0,
stepTime: 0,
data: [],
rootData: [
{
type: 'stair',
level: 1,
text: '隧道全程桩号',
fixed: 'xy',
sort: -1
},
{
type: 'stair',
level: 1,
text: '设计围岩级别',
fixed: 'y',
data: [
{
start: 0,
end: 100,
level: 0
}
]
}
],
fixedData:
'[{"type":"multistage","level":1,"text":"施工进度","expand":true,"subList":[{"text":"开挖","tip":"100%","level":2,"color":"#2D8CF0","stripe":"thick","data":[{"start":0,"end":100,"level":0},{"start":100,"end":200,"level":3},{"start":200,"end":350,"level":2},{"start":370,"end":480,"level":4},{"start":480,"end":810,"level":5}]},{"text":"二次衬砌","tip":"5%","stripe":"thick","level":2}]},{"type":"multistage","level":1,"text":"质量检查","expand":false,"subList":[{"text":"初期支护","tip":"10%","level":2,"stripe":"thin","data":[{"start":0,"end":100,"level":0},{"start":100,"end":200,"level":3},{"start":200,"end":350,"level":2},{"start":370,"end":480,"level":4},{"start":480,"end":810,"level":5}]},{"text":"二次衬砌","tip":"5%","stripe":"thin","level":2}]},{"type":"multistage","level":1,"text":"工序影像","expand":true,"subList":[{"level":2,"text":"明洞衬砌","info":"明洞及洞门","data":[{"start":50,"end":54,"level":0},{"start":100,"end":200,"level":3},{"start":200,"end":350,"level":2},{"start":370,"end":480,"level":4},{"start":480,"end":810,"level":1},{"start":980,"end":1100,"level":5},{"start":1480,"end":1550,"level":5},{"start":2480,"end":2500,"level":3}],"expand":true,"subList":[{"text":"基础","level":3},{"text":"钢筋","level":3}]},{"level":2,"text":"明洞及洞门","info":"明洞及洞门","expand":false,"data":[{"start":200,"end":350,"level":2},{"start":370,"end":480,"level":4},{"start":480,"end":810,"level":1},{"start":980,"end":1100,"level":5},{"start":1480,"end":1550,"level":5},{"start":2480,"end":2500,"level":3}],"subList":[{"text":"基础","level":3,"data":[{"start":380,"end":600,"level":3},{"start":5400,"end":5500,"level":4}]},{"text":"钢筋","level":3}]}]}]',
extData: '[{"type":"multistage","level":1,"text":"施工进度","expand":true,"subList":[{"text":"开挖","tip":"100%","level":2,"color":"#2D8CF0","stripe":"thick","data":[{"start":0,"end":100,"level":0},{"start":100,"end":200,"level":3},{"start":200,"end":350,"level":2},{"start":370,"end":480,"level":4},{"start":480,"end":810,"level":5}]},{"text":"二次衬砌","tip":"5%","stripe":"thick","level":2}]},{"type":"multistage","level":1,"text":"质量检查","expand":false,"subList":[{"text":"初期支护","tip":"10%","level":2,"stripe":"thin","data":[{"start":0,"end":100,"level":0},{"start":100,"end":200,"level":3},{"start":200,"end":350,"level":2},{"start":370,"end":480,"level":4},{"start":480,"end":810,"level":5}]},{"text":"二次衬砌","tip":"5%","stripe":"thin","level":2}]}]',
rules: {},
tableLine: {},
shaft: {},
startPileNo: 150,
endPileNo: 700,
startPileStr: '',
endPileStr: '',
current: null,
rowIndex: -1,
tunnelWidth: 0,
tableWidth: 0,
tableHeight: 0,
contentStartX: 0,
contentEndX: 0,
contentPosition: [0, 0, 0, 0],
oldDownX: 0,
oldDownY: 0,
mouseDownX: 0,
mouseDownY: 0,
lineTableData: []
}
},
computed: {
kMax() {
return Math.floor(this.endPileNo / 1000)
},
vMax() {
if (this.filterData.k === this.kMax) {
return this.endPileNo % 1000
} else {
return 999.9
}
},
kMin() {
return Math.floor(this.startPileNo / 1000)
},
vMin() {
return this.startPileNo % 1000
},
ruleHeight() {
return this.rowHeight * 0.48
},
tipHeight() {
return this.rowHeight * 0.52
},
lineRectData() {
return this.lineTableData.filter(v => v.data || (!v.expand && v.fixed !== 'xy'))
}
},
watch: {
meterToPx(val) {
this.calcTunnelWidth()
this.preMeterToPx = val
},
current: {
handler(val, old) {
old && this.render(old)
if (val) {
this.canvasWrap.style.cursor = 'pointer'
this.render(val)
} else {
this.$refs.canvasWrap.style.cursor = ''
}
this.calcTooltipStyle()
},
deep: true
}
},
mounted() {
this.getData()
window.onresize = this.listenVessel
},
methods: {
getData() {
this.originData = JSON.parse(JSON.stringify(data))
this.shaft = {
originData: data.slopeAndShaftList || []
}
this.descriptionWrapWidth = this.$refs.description.getBoundingClientRect().width - 48
this.descriptionWidth = this.getTextWidth(data.tunnelIntroduction, 'font-size: 15px').width
this.tunnelIntroduction = data.tunnelIntroduction
this.startPileNo = data.startPileNo >>> 0
this.endPileNo = data.endPileNo >>> 0
this.calcTunnelWidth()
this.$nextTick(() => {
this.filterData.k = this.kMin
this.filterData.v = this.vMin
})
this.startPileStr = `进口 ${data.startPileStr}`
this.endPileStr = `出口 ${data.endPileStr}`
this.formatData()
this.canvasWrap ? this.renderAll() : this.init()
setTimeout(() => {
this.$refs.tableWrap.style.pointerEvents = ''
}, 100)
},
formatData() {
this.data = JSON.parse(JSON.stringify(this.rootData))
if (this.originData.designInfoList) {
this.data[1].data = (this.originData.designInfoList || []).map(item => {
item.centerName = item.levelName
item.piles = [item.startPileNo - this.startPileNo, item.endPileNo - this.startPileNo]
item.end = item.piles[1]
item.start = item.piles[0]
item.level = item.levelType
let upper = (item.levelCode || '').toLocaleUpperCase()
let inner = upper.match(/[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]/)[0]
item.tooltip = item.centerName
item.level = this.romeNumber.indexOf(inner)
return item
})
}
this.data.push(...JSON.parse(this.extData))
if (this.originData.processIma && this.originData.processIma.length) {
this.data.push({
text: '工序影像',
expand: true,
level: 1,
type: 'multistage',
subList: this.recursiveFormatting(this.originData.processIma, 2, 'processIma')
})
}
this.calcLineTableData()
},
recursiveFormatting(item = [], level, category = '') {
item.map(v => {
v.text = v.name
v.expand = false
v.level = level
if (level === 2 && v.subList) {
let sub1 = (v.subList.splice(0, 1) || [{}])[0]
v.dataList = sub1.dataList
v.text = sub1.name
v.info = v.name
}
v.data = (v.dataList || []).map(v2 => {
if (v2.endPileStr) {
v2.piles = [v2.startPileNo - this.startPileNo, v2.endPileNo - this.startPileNo]
v2.end = v2.piles[1]
v2.tooltip = `桩号范围:${v2.startPileStr}~${v2.endPileStr}`
} else {
v2.piles = [v2.startPileNo - this.startPileNo]
v2.tooltip = `桩号:${v2.startPileStr}`
}
v2.category = category
v2.level = v2.scoreLevel || v2.level
v2.start = v2.piles[0]
return v2
})
v.subList && v.subList.length && this.recursiveFormatting(v.subList, level + 1, category)
return v
})
return item
},
scrollHandle(e) {
this.translateY = -e.target.scrollTop
this.calcContentXY()
this.renderRule()
this.renderNodes()
},
scrollXHandle(e) {
this.translateX = -e.target.scrollLeft
this.calcContentXY()
this.renderRule()
this.renderNodes()
this.renderFixed()
},
changeMeterHandle(e) {
if (this.preMeterToPx !== e) {
this.ruleStep = this.meterToPx * this.ruleStepVal
this.renderAll()
}
},
locationHandle() {
let pileNo = this.filterData.k * 1000 + this.filterData.v
let pileNoLength = pileNo - this.startPileNo
this.$refs.scrollX.scrollLeft = pileNoLength * this.meterToPx
},
calcTooltipStyle() {
clearTimeout(this.timer2)
this.timer2 = setTimeout(() => {
if (!this.current) return {}
let x = 0
let y = 0
let top = 0
let left = 0
this.lineStyle = { display: 'none' }
if (['rule', 'top'].includes(this.current.placement)) {
let w = this.getTextWidth(this.current.tooltip, 'padding: 5px; font-size: 14px;').width / 2
;[left, top] = this.current.tooltipXY
if (this.current.placement === 'rule') {
this.lineStyle = {
left: left + this.position.x + 'px',
top: this.position.y + 'px',
height: this.tableHeight + 'px',
maxHeight: this.wrapHeight + 'px'
}
}
left = left + this.position.x - w
top += this.position.y - 30
} else {
;[, y] = this.current.crd[0]
;[x] = this.current.crd[1]
let offset = 2
y += this.translateY
x += 0
left = this.position.x + x + offset
top = this.position.y + y + 10
}
this.tooltipStyle = {
left: left + 'px',
top: top + 'px'
}
}, 10)
},
changeRowHeight() {
this.$nextTick(() => {
this.renderAll()
})
},
init() {
this.canvasWrap = this.$refs.canvasWrap
this.canvasWrap.style.backgroundColor = '#ffffff'
this.canvasWrap.className = `${this.canvasWrap.className} canvas-container`
this.tableFixed = document.createElement('canvas')
this.tableMenu = document.createElement('canvas')
this.tableRule = document.createElement('canvas')
this.chart = document.createElement('canvas')
this.canvasWrap.appendChild(this.chart)
this.canvasWrap.appendChild(this.tableRule)
this.canvasWrap.appendChild(this.tableMenu)
this.canvasWrap.appendChild(this.tableFixed)
this.ctx = this.ctx || this.chart.getContext('2d')
this.ctxMenu = this.ctxMenu || this.tableMenu.getContext('2d')
this.ctxFixed = this.ctxFixed || this.tableFixed.getContext('2d')
this.ctxRule = this.ctxRule || this.tableRule.getContext('2d')
let img = new Image()
img.src = this.minStripe
img.onload = () => {
this.minStripeColor = this.ctx.createPattern(img, 'repeat')
}
let img2 = new Image()
img2.src = this.maxStripe
img2.onload = () => {
this.maxStripeColor = this.ctx.createPattern(img2, 'repeat')
}
this.listenVessel()
this.$nextTick(() => {
this.initEvent()
})
},
initEvent() {
this.position = this.canvasWrap.getBoundingClientRect()
this.canvasWrap.addEventListener('mousemove', this.addMouseMove)
this.canvasWrap.addEventListener('mouseleave', this.addMouseLeave)
this.canvasWrap.addEventListener('click', this.addMouseClick)
this.canvasWrap.addEventListener('mousedown', this.addMouseDown)
document.addEventListener('mouseup', this.addMouseUp)
this.canvasWrap.addEventListener('mouseenter', this.addMouseWhell)
},
listenVessel() {
clearTimeout(this.timer)
if (this.canvasWrap) {
this.timer = setTimeout(() => {
this.tableRule.width = this.tableFixed.width = this.chart.width = this.tableMenu.width = this.wrapWidth = this.canvasWrap.clientWidth
this.tableRule.height = this.tableFixed.height = this.chart.height = this.tableMenu.height = this.wrapHeight = this.canvasWrap.clientHeight
this.renderAll()
}, 50)
}
},
addMouseMove(e) {
this.targetX = e.offsetX
this.targetY = e.offsetY
let x = this.targetX * this.scale
let y = this.targetY * this.scale
let xs = x - this.translateX * this.scale
let ys = y - this.translateY * this.scale
let flag = false
switch (true) {
case this.targetY < this.ruleHeight && this.targetX > this.colWidth:
flag = true
var base = this.ruleStepVal / this.ruleStep
var length = (this.targetX - this.colWidth) * base - this.translateX * base + this.startPileNo
this.current = {
tooltip: `K${Math.floor(length / 1000)}+${Math.round(length % 1000)}`,
placement: 'rule',
tooltipXY: [this.targetX, 0]
}
break
case this.targetY < this.rowHeight:
break
case this.targetX < this.colWidth && this.targetY > this.rowHeight * 2:
this.lineTableData.forEach(v => {
if (!v.fixed && this.isInnerPolygon([x, ys], v.crd)) {
flag = true
this.current = v
}
})
break
case this.targetY < this.rowHeight * 2 && this.targetX > this.colWidth:
var rowLine = this.lineRectData[0]
rowLine &&
rowLine.rect.forEach(v => {
if (this.isInnerPolygon([xs, y], v.crd)) {
flag = true
v.placement = 'top'
v.tooltipXY = [this.targetX, v.crd[1][1]]
this.current = v
}
})
break
case this.targetY > this.rowHeight * 2 && this.targetX > this.colWidth:
var rowIndex = Math.floor(ys / this.rowHeight) - 1
var row = this.lineRectData[rowIndex]
row &&
row.rect.forEach(v => {
if (this.isInnerPolygon([xs, ys], v.crd)) {
flag = true
v.placement = 'top'
v.tooltipXY = [this.targetX, v.crd[1][1] + this.translateY]
this.current = v
}
})
}
if (!flag) this.current = null
},
addMouseLeave() {
this.current = null
this.removeMouseWhell()
},
addMouseClick(e) {
if (Math.abs(this.mouseDownX - e.offsetX) > 2 || Math.abs(this.mouseDownY - e.offsetY) > 2) {
return
}
switch (true) {
case this.current && this.current.expand !== undefined:
this.current.expand = !this.current.expand
this.calcLineTableData()
this.renderAll()
break
case this.current && !!this.current.id && this.current.category === 'processIma':
this.modalInfo = {
modal: true,
title: '详情',
id: this.current.id,
readonly: true
}
}
},
addMouseWhell() {
this.canvasWrap.addEventListener('mousewheel', this.scrollFunc, { passive: false })
},
removeMouseWhell() {
this.canvasWrap.removeEventListener('mousewheel', this.scrollFunc, { passive: false })
},
scrollFunc(e) {
e.preventDefault()
if (e.wheelDelta) {
let ca = e.wheelDelta > 0 ? -1 : 1
let isScroll = this.tableHeight > this.wrapHeight || this.tableWidth > this.wrapWidth
if (e.shiftKey && this.tableWidth > this.wrapWidth) {
this.$refs.scrollX.scrollLeft = this.$refs.scrollX.scrollLeft + this.scrollStep * ca * (e.altKey ? 8 : 1)
} else if (e.ctrlKey) {
this.meterToPx -= ca * this.stepPx * (e.altKey ? 4 : 1)
this.meterToPx = this.meterToPx < this.meterToPxMin ? this.meterToPxMin : this.meterToPx
this.meterToPx = this.meterToPx > this.meterToPxMax ? this.meterToPxMax : this.meterToPx
let base = this.ruleStepVal / this.ruleStep
let length = (e.offsetX - this.colWidth) * base - this.translateX * base
this.ruleStep = this.meterToPx * this.ruleStepVal
if (this.meterToPx < this.meterToPxMax) {
this.current = null
this.$refs.scrollX.scrollLeft = this.$refs.scrollX.scrollLeft - length * this.stepPx * ca * (e.altKey ? 4 : 1)
}
this.renderAll()
} else if (isScroll) {
this.current = null
this.$refs.scrollY.scrollTop = this.$refs.scrollY.scrollTop + this.scrollStep * ca
}
}
},
addMouseDown(e) {
this.oldDownX = e.pageX
this.oldDownY = e.pageY
this.mouseDownX = e.offsetX
this.mouseDownY = e.offsetY
clearTimeout(this.timer)
if (e.offsetX > this.colWidth && e.offsetY > this.rowHeight * 2) {
document.body.style.cursor = 'grabbing'
document.onselectstart = () => false
document.ondragstart = () => false
document.addEventListener('mousemove', this.dragCanvas)
}
},
addMouseUp() {
document.body.style.cursor = ''
document.onselectstart = () => null
document.ondragstart = null
document.removeEventListener('mousemove', this.dragCanvas)
if (Date.now() - this.stepTime > 10) {
return
}
let xMax = this.animationStepX * 60
let yMax = this.animationStepY * 60
let x = this.animationStepX
let y = this.animationStepY
let offsetX = 0
let offsetY = 0
let animationMove = () => {
let oldX = offsetX
let oldY = offsetY
offsetX = quadraticOut(this.animationStepX / xMax) || 0
offsetY = quadraticOut(this.animationStepY / yMax) || 0
this.animationStepX += x
this.animationStepY += y
let left = this.$refs.scrollX.scrollLeft
let top = this.$refs.scrollY.scrollTop
let caX = (offsetX - oldX) * xMax
let caY = (offsetY - oldY) * yMax
this.$refs.scrollX.scrollLeft -= caX
this.$refs.scrollY.scrollTop -= caY
if (left === this.$refs.scrollX.scrollLeft && top === this.$refs.scrollY.scrollTop) {
return
}
if (caX > -1 && caX < 1 && caY > -1 && caY < 1) {
return
}
if (this.animationStepX !== xMax || this.animationStepY !== yMax) {
this.timer = setTimeout(() => {
animationMove()
}, 25)
}
}
animationMove()
},
dragCanvas(e) {
let x = e.pageX
let y = e.pageY
let offsetX = x - this.oldDownX
let offsetY = y - this.oldDownY
this.animationStepX = offsetX
this.animationStepY = offsetY
this.stepTime = Date.now()
this.oldDownX = x
this.oldDownY = y
this.$refs.scrollX.scrollLeft -= offsetX
this.$refs.scrollY.scrollTop -= offsetY
},
calcRender() {
this.rowIndex = -1
let recursion = arr => {
arr.forEach((item, i) => {
this.rowIndex++
item.ado = []
item.rect = []
item.data && this.calcRect(item)
switch (true) {
case item.type === 'multistage':
this.calcTableVertical(item, i)
break
case item.type === 'stair':
this.calcTableStair(item, i)
break
case [2, 3].includes(item.level):
this.calcTableLevel23(item, i)
break
}
item.expand !== false && item.subList && item.subList.length && recursion(item.subList)
})
}
recursion(this.data)
this.tableLine.ado = []
for (let i = 1; i <= this.rowIndex; i++) {
let y = (i + 1) * this.rowHeight + this.offset
this.tableLine.ado.push({
type: 'line',
crd: [
[this.colWidth, y],
[this.wrapWidth, y]
],
strokeStyle: this.lineColor,
ctx: this.ctxMenu
})
}
this.tableWidth = this.tunnelWidth
this.tableHeight = this.rowHeight * (this.rowIndex + 1) + 2
this.calcContentXY()
},
calcContentXY() {
this.contentStartX = -this.translateX
this.contentEndX = this.wrapWidth - this.translateX
this.contentStartY = -this.translateY
this.contentEndY = this.wrapHeight - this.translateY
this.contentPosition = [
[this.contentStartX, this.contentStartY],
[this.contentEndX, this.contentStartY],
[this.contentEndX, this.contentEndY],
[this.contentStartX, this.contentEndY]
]
},
calcRect(item, ctxAll) {
let ry = this.rowIndex * this.rowHeight
let ctx = ctxAll || (item.fixed === 'y' ? this.ctxRule : this.ctx)
let color = item.fixed === 'y' ? this.levelColor : this.rectColor
let hoverColor = item.fixed === 'y' ? this.levelColor : this.rectHoverColor
item.data.forEach(v => {
let rx = v.start * this.meterToPx + this.colWidth + 0.5
let rMx = 0
if (!v.end) {
rMx = rx + this.faceWidth - 0.5
} else {
rMx = v.end * this.meterToPx + this.colWidth + 0.5
rMx += rx === rMx ? this.zeroWidth : 0
}
let crd = [
[rx, ry + 1.5],
[rMx, ry + 1.5],
[rMx, ry + this.rowHeight - 0.5],
[rx, ry + this.rowHeight - 0.5]
]
let ext = {}
if (item.stripe === 'thick') {
ext.fillStyle = item.color
ext.fillStripe = this.maxStripeColor
ext.hoverFillStyle = undefined
} else if (item.stripe === 'thin') {
ext.fillStripe = this.minStripeColor
ext.hoverFillStyle = undefined
}
if (!v.end) {
ext.borderRadius = 4
}
let rectExt = {}
if (v.tooltip) {
rectExt.tooltip = v.tooltip
}
let extData = []
if (v.levelCode) {
extData.push({
type: 'text',
text: v.centerName,
crd: [rx + (rMx - rx) / 2, ry + this.rowHeight / 2],
fillStyle: '#ffffff',
ctx
})
}
item.rect.push({
crd,
ado: [
{
type: 'polygon',
crd,
fillStyle: color[v.level % color.length],
hoverFillStyle: hoverColor[v.level % hoverColor.length],
strokeStyle: '#FFFFFF',
ctx,
...ext
},
...extData
],
text: item.text,
id: v.id,
category: v.category,
...rectExt
})
})
},
calcTableVertical(item, i, ctxAll) {
let ctx = ctxAll || this.ctxMenu
let { x, y } = this.destruction()
let num = item.expand ? 0 : 1
item.expand &&
item.subList.forEach(v => {
num++
v.expand &&
v.subList &&
v.subList.length &&
v.subList.forEach(() => {
num++
})
})
let height = num * this.rowHeight
let yMax = y + height
let ext = {}
item.tooltip = ''
if (!item.expand) {
this.calcTableStair(item, i, this.ctxMenu)
item.ado[0].hoverFillStyle = this.tableHoverBgColor
let yc = y + this.rowHeight / 2 - 3
let xc = x + 13
item.ado.push({
type: 'line',
crd: [
[xc, yc],
[xc + 6, yc + 6],
[xc + 12, yc]
],
strokeStyle: '#e8eaec',
lineWidth: 2,
lineCap: 'round',
ctx
})
return
} else {
let textw = this.getTextWidth(item.text, `font-size: ${this.fontSize}px`).width
if (textw > height) {
ext.text = item.text.substr(0, num * 2)
item.tooltip = item.text
}
}
let xMax = x + this.colw
let xHalf = xMax / 2
item.crd = [
[x, y],
[xMax, y],
[xMax, yMax],
[x, yMax]
]
item.ado.push({
type: 'polygon',
crd: item.crd,
fillStyle: this.tableBgColor,
hoverFillStyle: this.tableHoverBgColor,
ctx,
ext
})
let arr = ext.text || item.text
let lineHeight = this.fontSize * 1.3
let y2 = y + height / 2 - lineHeight * (arr.length / 2) + 12
arr.split('').forEach((v, i2) => {
item.ado.push({
type: 'text',
text: v,
crd: [xHalf, y2 + i2 * lineHeight],
font: `bold ${this.fontSize}px ${this.fontFamily}`,
ctx
})
})
item.ado.push({
type: 'line',
crd: [
[xHalf - 6, y + 16],
[xHalf, y + 10],
[xHalf + 6, y + 16]
],
lineWidth: 2,
strokeStyle: '#e8eaec',
lineCap: 'round',
ctx
})
this.rowIndex--
},
calcTableStair(item, i, ctx) {
let { x, y } = this.destruction()
item.crd = [
[x, y],
[x, y + this.rowHeight],
[x + this.colWidth, y + this.rowHeight],
[x + this.colWidth, y]
]
item.ado.push({
type: 'polygon',
crd: item.crd,
fillStyle: this.tableBgColor,
ctx: ctx || this.ctxFixed
})
item.ado.push({
type: 'text',
text: item.text,
crd: [(x + this.colWidth) / 2, y + this.rowHeight / 2],
font: `bold ${this.fontSize}px ${this.fontFamily}`,
ctx: ctx || this.ctxFixed
})
},
calcTableLevel23(item, i, ctxAll) {
let ctx = ctxAll || this.ctxMenu
let { x, y } = this.destruction()
item.crd = [
[x + this.colw, y],
[x + this.colWidth, y],
[x + this.colWidth, y + this.rowHeight],
[x + this.colw, y + this.rowHeight]
]
item.ado.push({
type: 'polygon',
crd: item.crd,
fillStyle: item.level === 3 ? this.tableLevel3BgColor : this.tableBgColor,
hoverFillStyle: ['', '', this.tableHoverBgColor, this.tableLevel3BgColor][item.level],
ctx
})
let w2 = (x + this.colWidth - this.colw) / 2 + this.colw
if (item.tip) {
let tipw = this.getTextWidth(item.tip, `font-size: ${this.fontSize}px`).width / 2 + 2
let textw = this.getTextWidth(item.text, `font-size: ${this.fontSize}px`).width / 2 + 2
item.ado.push({
type: 'text',
text: item.text + '(',
crd: [w2 - tipw, y + this.rowHeight / 2],
ctx
})
item.ado.push({
type: 'text',
text: item.tip,
crd: [w2 + textw, y + this.rowHeight / 2],
fillStyle: '#2D8CF0',
ctx
})
item.ado.push({
type: 'text',
text: ')',
crd: [w2 + textw + tipw + 2, y + this.rowHeight / 2],
ctx
})
} else if (item.info) {
item.ado.push({
type: 'text',
text: item.info,
font: `bold 13px ${this.fontFamily}`,
crd: [w2, y + this.rowHeight / 3 - 1.5],
ctx
})
item.ado.push({
type: 'text',
text: item.text,
font: `${this.fontSize * 0.8}px ${this.fontFamily}`,
crd: [w2, y + (this.rowHeight / 4) * 3 + 1.5],
ctx
})
let pm = item.expand ? 1 : -1
; (item.subList || []).length &&
item.ado.push({
type: 'polygon',
crd: [
[w2 - 5, y + this.rowHeight / 2 - 3 * pm],
[w2 + 5, y + this.rowHeight / 2 - 3 * pm],
[w2, y + this.rowHeight / 2 + 3 * pm]
],
fillStyle: item.expand ? '#4FA5FF' : '#D3DCE6',
stroke: false,
ctx
})
} else {
let ext = {}
let textw = this.getTextWidth(item.text, `font-size: ${this.fontSize}px`).width
if (textw > this.colWidth - this.colw) {
ext.text = item.text.substr(0, 7) + '...'
item.tooltip = item.text
}
item.ado.push({
type: 'text',
text: item.text,
font: `${this.fontSize * 0.8}px ${this.fontFamily}`,
crd: [w2, y + this.rowHeight / 2],
ctx,
...ext
})
}
},
calcLineTableData() {
let newArr = []
let recursion = arr => {
arr.forEach(item => {
newArr.push(item)
item.expand !== false && item.subList && item.subList.length && recursion(item.subList)
})
}
recursion(this.data)
this.lineTableData = newArr
},
calcTunnelWidth() {
this.tunnelWidth = (this.endPileNo - this.startPileNo) * this.meterToPx + this.colWidth + 20
},
destruction() {
return {
x: this.lineWidth - this.offset,
y: this.rowIndex * this.rowHeight + this.lineWidth - this.offset
}
},
renderAll() {
console.log('全局绘制(重绘)')
this.$nextTick(() => {
this.calcRender()
this.renderRule()
this.renderNodes()
this.renderFixed()
})
},
renderNodes() {
this.ctxFixed.clearRect(0, 0, this.colWidth, this.wrapHeight)
this.tableMenu.width = this.tableMenu.width
this.ctxMenu.setTransform(1, 0, 0, 1, 0, this.translateY)
let yMin = 2 * this.rowHeight - this.translateY
let yMax = this.wrapHeight - this.translateY
this.ctxMenu.rect(0, yMin, this.wrapWidth, yMax)
this.ctxMenu.clip()
this.lineTableData.forEach(item => {
this.render(item)
})
if (this.translateY) {
this.renderScrollShadow(this.ctxMenu, [0, yMin - 10, this.wrapWidth, 10])
}
if (this.translateX) {
this.$nextTick(() => {
this.renderScrollShadow(this.ctxRule, [this.colWidth - this.translateX - 10, 0, 10, this.rowHeight * (this.rowIndex + 1)])
})
}
this.$nextTick(() => {
this.render(this.tableLine)
})
this.chart.width = this.chart.width
this.ctx.setTransform(1, 0, 0, 1, this.translateX, this.translateY)
this.ctx.rect(this.colWidth + 1.5 - this.translateX, this.rowHeight * 2 + this.offset - this.translateY, this.wrapWidth, this.rowHeight * (this.rowIndex - 1))
this.ctx.clip()
this.ctx.save()
this.ctx.fillStyle = this.innerWrapBgColor
this.ctx.fill()
this.ctx.restore()
this.lineRectData.forEach(item => {
if (!item) return
let [, y] = item.crd[0]
let boolY = y <= this.contentEndY && y >= this.contentStartY
if (item.fixed === 'y') boolY = true
boolY &&
item.rect.forEach(v => {
let [x] = v.crd[0]
let [xMax] = v.crd[1]
let boolX = (x > this.contentEndX && xMax > this.contentEndX) || (x < this.contentStartX && xMax < this.contentStartX)
!boolX && this.render(v)
})
})
},
renderScrollShadow(ctx, rect) {
ctx.save()
ctx.shadowBlur = 10
ctx.fillStyle = 'red'
ctx.shadowColor = 'rgba(0, 0, 0, .3)'
ctx.fillRect(...rect)
ctx.restore()
},
render(item, ctx) {
let params = {}
; (item.ado || []).forEach(v => {
switch (v.type) {
case 'text':
params = { ...v }
if (ctx) {
params.ctx = ctx
}
this.drawText(params)
break
case 'polygon':
params = { ...v }
if (this.current === item && params.hoverFillStyle) {
params.fillStyle = params.hoverFillStyle
}
if (ctx) {
params.ctx = ctx
}
this.drawPolygon(params)
break
case 'line':
params = { ...v }
if (ctx) {
params.ctx = ctx
}
this.drawLine(params)
break
}
})
},
renderRule(ctxAll) {
let ctx = ctxAll || this.ctxRule
if (!ctxAll) {
this.tableRule.width = this.tableRule.width
ctx.setTransform(1, 0, 0, 1, this.translateX, 0)
ctx.rect(this.colWidth - this.translateX, 0, this.wrapWidth, this.wrapHeight)
ctx.clip()
}
let start = Math.floor(Math.abs(this.translateX) / this.ruleStep) + 1
let end = Math.ceil((Math.abs(this.translateX) + this.wrapWidth) / this.ruleStep) + 1
this.rules.ado = []
this.rules.startX = start + this.startPileNo
for (let i = start; i < end; i++) {
let x = this.colWidth + i * this.ruleStep - this.offset
this.rules.ado.push({
type: 'line',
crd: [
[x, this.ruleHeight],
[x, this.ruleHeight + this.tickLength]
],
ctx
})
let y = this.ruleHeight / 2 + 2
let text = i * this.ruleStepVal
this.rules.ado.push({
type: 'text',
crd: [x, y],
text: text,
fillStyle: this.tickTextColor,
font: `${this.tickFontSize * (this.ruleStep >= 100 ? 1.1 : 1)}px ${this.fontFamily}`,
ctx
})
if (this.ruleStep >= 100) {
let num = 0
if (this.ruleStep >= 100) num = 2
if (this.ruleStep >= 200) num = 5
if (this.ruleStep >= 400) num = 10
if (this.ruleStep >= 800) num = 20
if (this.ruleStep >= 1500) num = 50
if (this.ruleStep >= 3000) num = 100
let step = this.ruleStep / num
for (let j = 1; j < num; j++) {
let xm = x + step * j - this.ruleStep
this.rules.ado.push({
type: 'text',
crd: [xm, y],
text: text + (this.ruleStepVal / num) * j - this.ruleStepVal,
fillStyle: this.tickTextColor,
font: `${this.tickFontSize * 0.75}px ${this.fontFamily}`,
ctx
})
this.rules.ado.push({
type: 'line',
crd: [
[xm, this.ruleHeight],
[xm, this.ruleHeight + this.tickLength - 4]
],
ctx
})
}
}
}
this.render(this.rules)
this.shaft.ado = []
let color = '#90A5BA'
; (this.shaft.originData || []).forEach(v => {
let x = this.pileNoToCrd(v.startPileNo) + this.colWidth
this.shaft.ado.push({
type: 'line',
crd: [
[x, 0],
[x, this.tableHeight]
],
ctx,
strokeStyle: '#FFFFFF',
lineWidth: 2
})
this.shaft.ado.push({
type: 'line',
crd: [
[x, 0],
[x, this.tableHeight]
],
ctx,
strokeStyle: color,
lineWidth: 2,
setLineDash: [2, 2]
})
this.shaft.ado.push({
type: 'text',
text: `${v.name} ${v.startPileStr}`,
crd: [x + 40, this.ruleHeight + this.tipHeight / 2 + 2],
textAlign: 'start',
fillStyle: this.pileStrColor,
ctx
})
this.shaft.ado.push({
type: 'polygon',
crd: [
[x - 6, 0],
[x + 6, 0],
[x, 7]
],
fillStyle: color,
stroke: false,
ctx
})
let y = this.ruleHeight
;[x, x - 25].forEach((v, i) => {
let xMax = v + this.tipHeight
let yMax = y + this.tipHeight
let xx = i ? xMax : v
let xd = !i ? xMax : v
let ca = i ? -1 : 1
let x2 = xx + 8 * ca
let x3 = xx + 18 * ca
let crd = [
[x2, yMax - (this.ruleHeight / 2 - 3)],
[x3, yMax - (this.ruleHeight / 2 - 3)],
[x3, yMax - (this.ruleHeight / 2 - 6)],
[xd, yMax - this.ruleHeight / 2],
[x3, yMax - (this.ruleHeight / 2 + 6)],
[x3, yMax - (this.ruleHeight / 2 + 3)],
[x2, yMax - (this.ruleHeight / 2 + 3)],
[x2, y]
]
this.shaft.ado.push({
type: 'polygon',
crd,
fillStyle: color,
ctx
})
})
})
this.$nextTick(() => {
this.render(this.shaft)
})
},
renderFixed(ctxAll) {
let ctx = ctxAll || this.ctxFixed
!ctxAll && ctx.clearRect(this.colWidth + 1, 0, this.wrapWidth, this.rowHeight * 2)
;[this.lineWidth, this.ruleHeight, this.rowHeight].forEach(v => {
v -= this.offset
this.drawLine({
crd: [
[this.colWidth, v],
[this.wrapWidth, v]
],
ctx
})
})
let x0 = this.colWidth - this.offset + 1
let y0 = this.ruleHeight - this.offset
let start = Math.round((Math.abs(this.translateX) / this.ruleStep) * 100) + this.startPileNo
let end = Math.round(((Math.abs(this.translateX) + this.wrapWidth - this.colWidth) / this.ruleStep) * 100) + this.startPileNo
let startPileNo = `K${Math.floor(start / 1000)}+${start % 1000}`
let endPileNo = `K${Math.floor(end / 1000)}+${end % 1000}`
;[x0, this.wrapWidth - this.ruleHeight - this.offset].forEach((v, i) => {
let xMax = v + this.tipHeight
let yMax = y0 + this.tipHeight
this.drawPolygon({
crd: [
[v, y0],
[xMax, y0],
[xMax, yMax],
[v, yMax]
],
fillStyle: this.inOutIconBgColor,
ctx
})
let xx = i ? xMax : v
let xd = !i ? xMax : v
let ca = i ? -1 : 1
let xl = this.tipHeight * 0.23
let x1 = xx + xl * ca
let x2 = xx + (xl + 2) * ca
let x3 = xx + (this.tipHeight - 4) * ca
let crd = [
[x1, y0],
[x1, yMax],
[x2, yMax],
[x2, yMax - (this.ruleHeight / 2 - 1)],
[x3, yMax - (this.ruleHeight / 2 - 1)],
[x3, yMax - (this.ruleHeight / 2 - 4)],
[xd, yMax - this.ruleHeight / 2],
[x3, yMax - (this.ruleHeight / 2 + 4)],
[x3, yMax - (this.ruleHeight / 2 + 1)],
[x2, yMax - (this.ruleHeight / 2 + 1)],
[x2, y0]
]
this.drawPolygon({
crd,
fillStyle: this.inOutIconColor,
ctx
})
let ext = {}
if (i) {
v += this.ruleHeight
ext.textAlign = 'right'
}
this.drawText({
crd: [v + (this.ruleHeight + 10) * ca, y0 + this.tipHeight / 2 + 2],
text: ca === 1 ? startPileNo : endPileNo,
fillStyle: this.pileStrColor,
textAlign: 'start',
ctx,
...ext
})
})
},
pileNoToCrd(pileNo) {
return (pileNo - this.startPileNo) * this.meterToPx || 0
},
drawPolygon(data, ctx) {
let arr = data.crd.concat()
ctx = data.ctx || ctx || this.ctx
ctx.save()
ctx.strokeStyle = data.strokeStyle || this.lineColor
ctx.fillStyle = data.fillStyle || this.fillStyle
ctx.lineWidth = data.lineWidth || this.lineWidth
if (data.hoverFillStyle) {
let xAll = arr.map(v => v[0])
let xMax = Math.max(...xAll)
let xMin = Math.min(...xAll)
let yAll = arr.map(v => v[1])
let yMax = Math.max(...yAll)
let yMin = Math.min(...yAll)
ctx.clearRect(xMin, yMin, xMax - xMin, yMax - yMin)
}
ctx.beginPath()
if (data.borderRadius) {
let r = data.borderRadius
let [x, y] = arr.shift()
let x1 = x - (x - arr[0][0]) / 2
ctx.moveTo(x1, y)
do {
let [x2, y2] = arr.shift()
let [x3, y3] = arr[0] || [x, y]
let xa = x2 - (x2 - x3) / 2
let ya = y2 - (y2 - y3) / 2
ctx.arcTo(x2, y2, xa, ya, r)
} while (arr.length)
ctx.arcTo(x, y, x1, y, r)
} else {
ctx.moveTo(...arr.shift())
do {
ctx.lineTo(...arr.shift())
} while (arr.length)
}
data.closePath !== false && ctx.closePath()
ctx.strokeStyle = data.strokeStyle
data.stroke !== false && ctx.stroke()
data.fillStyle && ctx.fill()
ctx.restore()
if (data.fillStripe) {
ctx.save()
let arr = data.crd.concat()
ctx.fillStyle = data.fillStripe
ctx.globalAlpha = data.globalAlpha || 1
ctx.beginPath()
ctx.moveTo(...arr.shift())
do {
ctx.lineTo(...arr.shift())
} while (arr.length)
ctx.closePath()
ctx.fill()
ctx.restore()
}
},
drawLine(data, ctx) {
let arr = data.crd.concat()
ctx = data.ctx || ctx || this.ctx
ctx.save()
ctx.strokeStyle = data.strokeStyle || this.lineColor
ctx.lineCap = data.lineCap || ctx.lineCap
ctx.lineWidth = data.lineWidth || this.lineWidth
data.setLineDash && ctx.setLineDash(data.setLineDash)
ctx.beginPath()
ctx.moveTo(...arr.shift())
do {
ctx.lineTo(...arr.shift())
} while (arr.length)
ctx.stroke()
ctx.restore()
},
drawText(data, ctx) {
ctx = data.ctx || ctx || this.ctx
ctx.save()
ctx.textBaseline = data.textBaseline || 'middle'
ctx.textAlign = data.textAlign || 'center'
ctx.font = data.font || `${this.fontSize}px ${this.fontFamily}`
ctx.fillStyle = data.fillStyle || this.fontColor
ctx.fillText(data.text, ...data.crd)
ctx.restore()
},
isInnerPolygon(point, vs) {
let [x, y] = point
let inside = false
for (let i = 0, j = vs.length - 1; i < vs.length; j = i++) {
let [xi, yi] = vs[i]
let [xj, yj] = vs[j]
let intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi
if (intersect) inside = !inside
}
return inside
},
getTextWidth(str, style) {
let div = document.createElement('div')
div.setAttribute('style', `position: fixed; left: -100000px; opacity: 0; ${style}`)
div.innerHTML = str
document.body.appendChild(div)
let position = div.getBoundingClientRect()
document.body.removeChild(div)
return position
},
renderAllNodesAndDownload() {
let allCanvas = document.createElement('canvas')
let ctx = allCanvas.getContext('2d')
let data = JSON.parse(JSON.stringify(this.data))
let oldRowIndex = this.rowIndex
this.rowIndex = -1
let recursionExpand = arr => {
arr.forEach(item => {
if (item.expand !== undefined) {
item.expand = true
}
item.subList && item.subList.length && recursionExpand(item.subList)
})
}
recursionExpand(data)
let oldTranslateX = this.translateX
this.translateX = 0
let rows = []
let allRows = []
let recursion = arr => {
arr.forEach((item, i) => {
allRows.push(item)
; (!item.subList || item.level !== 1) && rows.push(item)
this.rowIndex++
item.ado = []
item.rect = []
item.data && this.calcRect(item, ctx)
switch (true) {
case item.type === 'multistage':
this.calcTableVertical(item, i, ctx)
break
case item.type === 'stair':
this.calcTableStair(item, i, ctx)
break
case [2, 3].includes(item.level):
this.calcTableLevel23(item, i, ctx)
break
}
item.subList && item.subList.length && recursion(item.subList)
})
}
recursion(data)
this.rowIndex = oldRowIndex
let canvasWidth = this.tunnelWidth
let canvasHeight = rows.length * this.rowHeight
allCanvas.height = canvasHeight + 2
allCanvas.width = canvasWidth
let oldTableHeight = this.tableHeight
this.tableHeight = canvasHeight
let old = this.wrapWidth
this.wrapWidth = canvasWidth
ctx.fillStyle = this.innerWrapBgColor
ctx.fillRect(this.colWidth, this.rowHeight, canvasWidth, canvasHeight)
allRows.forEach(v => {
this.render(v, ctx)
v.rect &&
v.rect.forEach(v2 => {
this.render(v2, ctx)
})
})
this.renderRule(ctx)
this.renderFixed(ctx)
let tableLine = { ado: [] }
let len = Math.floor(canvasHeight / this.rowHeight) - 1
for (let i = 1; i <= len; i++) {
let y = (i + 1) * this.rowHeight + this.offset
tableLine.ado.push({
type: 'line',
crd: [
[this.colWidth, y],
[canvasWidth, y]
],
strokeStyle: this.lineColor
})
}
this.render(tableLine, ctx)
this.wrapWidth = old
this.translateX = oldTranslateX
this.tableHeight = oldTableHeight
this.$nextTick(() => {
let image = allCanvas.toDataURL('image/png')
console.log(image)
})
},
printHandle(canvas) {
let imgData = canvas.toDataURL('image/png')
let win = window.open()
win.document.write = `<img src='${imgData}'/>`
win.print()
}
},
destroyed() {
let wrap = this.canvasWrap
if (wrap) {
wrap.removeEventListener('mousemove', this.addMouseMove)
wrap.removeEventListener('mouseleave', this.addMouseLeave)
wrap.removeEventListener('mouseenter', this.addMouseWhell)
wrap.removeEventListener('mousedown', this.addMouseDown)
wrap.removeEventListener('click', this.addMouseClick)
document.removeEventListener('mouseup', this.addMouseUp)
document.removeEventListener('keydown', this.keyDownHandle)
document.removeEventListener('keyup', this.keyUpHandle)
}
}
})
<div class="statistic-analysis-wrapper" id="app">
<el-form class="filter-wrapper form-inline" :model="filterData" inline="inline" label-width="80px">
<el-form-item label="K" label-width="20px">
<el-input-number size="small" v-model="filterData.k" controls-position="right" :min="kMin" :max="kMax" :precision="0"></el-input-number>
</el-form-item>
<el-form-item label="+" label-width="20px">
<el-input-number size="small" v-model="filterData.v" controls-position="right" :min="vMin" :max="vMax" :step="1" :precision="1"></el-input-number>
</el-form-item>
<el-button size="small" type="primary" :disabled="data.length <= 2" @click="locationHandle">定位</el-button>
<el-tooltip content="因演示工具限制,无法下载,目前只在控制台中输出base64格式图片">
<el-button size="small" type="primary" @click="renderAllNodesAndDownload">导出分析图</el-button>
</el-tooltip>
<el-form-item label="行高" label-width="80px">
<el-select v-model="rowHeight" @change="changeRowHeight">
<el-option v-for="item in rowHegihtOptions" :key="item" :value="item" :label="item"></el-option>
</el-select>
</el-form-item>
</el-form>
<div class="content-wrapper">
<div class="header-info-box clearfix" :class="{expand: isHeaderExpand}">
<div class="inner-wrap">
<div class="tip-title">
<div class="current">操作</div>
<div>简介</div>
</div>
<div class="tip-info-content">
<div class="tunnel-name">{{tunnelName}}</div>
<div class="description" ref="description">{{tunnelIntroduction}}</div><span class="expand-btn" v-if="descriptionWidth > descriptionWrapWidth" @click="isHeaderExpand = !isHeaderExpand">{{isHeaderExpand ? '收起':'详情'}}<span class="el-icon-arrow-down"></span></span>
</div>
</div>
</div>
<div class="slider-wrap">
<el-slider v-model="meterToPx" @input="changeMeterHandle" :step="stepPx" :min="meterToPxMin" :max="meterToPxMax"></el-slider><span class="tip-text">缩放比例(1米 = {{meterToPx}}像素)</span>
</div>
<div class="table-wrapper" ref="tableWrap" style="pointer-events: none;">
<div class="canvas-container" ref="canvasWrap"></div>
<div class="dark-scrollbar vertical" ref="scrollY" @scroll="scrollHandle">
<div :style="{height: `${tableHeight}px`, width: '1px'}"></div>
</div>
<div class="dark-scrollbar horizontal" ref="scrollX" @scroll="scrollXHandle">
<div :style="{width: `${tableWidth}px`, height: '1px'}"></div>
</div>
</div><template v-if="current && current.tooltip">
<div class="tooltip-custom" :style="tooltipStyle">{{(current || {}).tooltip}}</div>
<div class="line-tip" :style="lineStyle"></div>
</template>
</div>
</div>
html,body {
height: 100%;
}
.canvas-container {
position: relative;
height: 100%;
}
.canvas-container canvas {
position: absolute;
pointer-events: none;
}
.statistic-analysis-wrapper {
height: 100%;
}
.statistic-analysis-wrapper .filter-wrapper {
padding-top: 13px;
padding-left: 50px;
height: 60px;
border-bottom: 1px solid #d5dde4;
box-sizing: border-box;
}
.statistic-analysis-wrapper .filter-wrapper .el-button {
margin-top: 5px;
}
.statistic-analysis-wrapper .content-wrapper {
height: calc(100% - 60px);
overflow: hidden;
}
.statistic-analysis-wrapper .content-wrapper .slider-wrap {
margin-left: 20px;
height: 29px;
margin-top: -9px;
display: flex;
}
.statistic-analysis-wrapper .content-wrapper .slider-wrap .el-slider {
width: 200px;
margin-right: 10px;
}
.statistic-analysis-wrapper .content-wrapper .slider-wrap .el-slider__runway {
margin-top: 7px;
}
.statistic-analysis-wrapper .content-wrapper .slider-wrap .el-slider__bar {
display: none;
}
.statistic-analysis-wrapper .header-info-box {
height: 100px;
position: relative;
z-index: 1002;
}
.statistic-analysis-wrapper .header-info-box .inner-wrap {
display: flex;
background-color: #fff;
padding: 20px 20px 10px;
}
.statistic-analysis-wrapper .header-info-box .tip-title {
width: 44px;
margin-right: 20px;
font-size: 14px;
flex-shrink: 0;
}
.statistic-analysis-wrapper .header-info-box .tip-title > div {
height: 24px;
text-align: center;
font-weight: bold;
color: #2d8cf0;
border: 1px solid #d3e1fe;
margin-bottom: -1px;
}
.statistic-analysis-wrapper .header-info-box .tip-title .current {
color: #ffffff;
background-color: #2d8cf0;
}
.statistic-analysis-wrapper .header-info-box .tip-info-content {
flex-shrink: 0;
width: calc(100% - 64px);
}
.statistic-analysis-wrapper .header-info-box .tip-info-content .tunnel-name {
height: 27px;
margin-top: -3px;
font-size: 18px;
font-weight: bold;
}
.statistic-analysis-wrapper .header-info-box .tip-info-content .description {
height: 20px;
overflow: hidden;
padding-right: 48px;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 15px;
color: #555555;
margin-top: 10px;
}
.statistic-analysis-wrapper .header-info-box .expand-btn {
position: relative;
margin-top: -20px;
float: right;
font-size: 15px;
color: #2d8cf0;
cursor: pointer;
}
.statistic-analysis-wrapper .header-info-box .expand-btn .el-icon-arrow-down {
float: right;
margin-top: 1px;
text-shadow: 0 5px 0;
}
.statistic-analysis-wrapper .header-info-box.expand .inner-wrap {
box-shadow: 0 0 13px 0 rgba(0,0,0,0.1);
}
.statistic-analysis-wrapper .header-info-box.expand .tip-info-content .description {
height: auto;
overflow: visible;
white-space: normal;
padding-right: 0;
}
.statistic-analysis-wrapper .header-info-box.expand .el-icon-arrow-down {
transform: rotate(180deg);
margin-top: 6px;
}
.statistic-analysis-wrapper .table-wrapper {
position: relative;
height: calc(100% - 130px);
margin: 0 20px;
padding-bottom: 20px;
box-sizing: border-box;
}
.statistic-analysis-wrapper .table-wrapper .dark-scrollbar {
position: absolute;
}
.statistic-analysis-wrapper .table-wrapper .dark-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, .2);
border-radius: 3px;
}
.statistic-analysis-wrapper .table-wrapper .dark-scrollbar::-webkit-scrollbar {
width: 12px;
height: 12px;
}
.statistic-analysis-wrapper .table-wrapper .dark-scrollbar.vertical {
top: 0;
right: -15px;
bottom: 20px;
overflow-y: auto;
}
.statistic-analysis-wrapper .table-wrapper .dark-scrollbar.horizontal {
left: 0;
right: 0;
bottom: 3px;
overflow-x: auto;
}
.statistic-analysis-wrapper .tooltip-custom {
position: fixed;
z-index: 1;
background-color: rgba(47,59,85,0.8);
color: #ffffff;
font-size: 14px;
padding: 5px;
border-radius: 4px;
pointer-events: none;
transition: all 0.1s;
}
.statistic-analysis-wrapper .line-tip {
position: fixed;
z-index: 1;
pointer-events: none;
border-right: 1px dotted #bbbbbb;
transition: all 0.1s;
}