Joe
11 months ago
9 changed files with 675 additions and 16 deletions
@ -0,0 +1,50 @@ |
|||
<template> |
|||
<ElSelect class='FormSelect' v-bind="$attrs" v-on="$listeners"> |
|||
<ElOption v-for="item in options" :key="item.value" :label="item.label" :value="item.value"> |
|||
<slot></slot> |
|||
</ElOption> |
|||
</ElSelect> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'FormSelect', |
|||
props: { |
|||
// options: { |
|||
// /** |
|||
// * { |
|||
// * value: any; |
|||
// * label: any; |
|||
// * }[] |
|||
// */ |
|||
// type: Array, |
|||
// default: () => [] |
|||
// } |
|||
}, |
|||
data() { |
|||
return { |
|||
options: [{ |
|||
value: '选项1', |
|||
label: '黄金糕' |
|||
}, { |
|||
value: '选项2', |
|||
label: '双皮奶' |
|||
}, { |
|||
value: '选项3', |
|||
label: '蚵仔煎' |
|||
}, { |
|||
value: '选项4', |
|||
label: '龙须面' |
|||
}, { |
|||
value: '选项5', |
|||
label: '北京烤鸭' |
|||
}], |
|||
value: '' |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang='scss' scoped> |
|||
.FormSelect {} |
|||
</style> |
@ -0,0 +1,47 @@ |
|||
<template> |
|||
<ElPopover class='Popover' v-bind="getBind" v-on="$listeners"> |
|||
<slot /> |
|||
<template #reference> |
|||
<slot name="reference" /> |
|||
</template> |
|||
</ElPopover> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'Popover', |
|||
computed: { |
|||
getBind() { |
|||
return { |
|||
trigger: 'hover', |
|||
placement: "left", |
|||
...this.$attrs, |
|||
popperClass: "Popover-Scope-Screen" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang='scss'> |
|||
div.el-popper.Popover-Scope-Screen { |
|||
background: linear-gradient(180deg, #005C79 0%, #009BCC 100%); |
|||
border: 0; |
|||
color: #fff; |
|||
font-size: 14px; |
|||
font-family: PingFang SC, PingFang SC; |
|||
font-weight: 400; |
|||
color: #FFFFFF; |
|||
|
|||
div.popper__arrow { |
|||
border-left-color: #00799f; |
|||
|
|||
&::after { |
|||
border-left-color: #00799f; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
<style lang='scss' scoped> |
|||
.Popover {} |
|||
</style> |
@ -1,20 +1,123 @@ |
|||
<template> |
|||
<Card class='CrowdnessIndicatorRankings' title="拥挤度指标排名情况"> |
|||
CrowdnessIndicatorRankings |
|||
<Card class='CrowdnessIndicatorRankings border' title="拥挤度指标排名情况"> |
|||
<div class="map"> |
|||
map |
|||
</div> |
|||
<div class="right border"> |
|||
<div :class="['item', { active: active === item.key }]" v-for="item in operation" :key="item.key" |
|||
@click="handleClick(item)"> |
|||
<Popover :disabled="item.key !== 'weather'"> |
|||
<div v-if="item.key === 'weather'"> |
|||
天气:晴 能见度:优 风向:西南 风力:1级 |
|||
</div> |
|||
<div class="label" slot="reference" |
|||
:style="{ backgroundImage: `url(${require(`./images/${item.key}${active === item.key ? '-active' : ''}.svg`)})` }" /> |
|||
</Popover> |
|||
</div> |
|||
</div> |
|||
</Card> |
|||
</template> |
|||
|
|||
<script> |
|||
import Card from "./../../components/Card.vue" |
|||
import Card from "./../../components/Card.vue"; |
|||
|
|||
import Popover from "@screen/components/Popover/index.vue" |
|||
|
|||
export default { |
|||
name: 'CrowdnessIndicatorRankings', |
|||
components: { |
|||
Card |
|||
Card, |
|||
Popover |
|||
}, |
|||
data() { |
|||
return { |
|||
active: "weather", |
|||
operation: [ |
|||
{ |
|||
key: 'weather', |
|||
label: "天气" |
|||
}, |
|||
{ |
|||
key: 'camera', |
|||
label: "摄像机" |
|||
}, |
|||
{ |
|||
key: 'bar', |
|||
label: "柱状图" |
|||
}, |
|||
{ |
|||
key: 'level', |
|||
label: "天气" |
|||
}, |
|||
{ |
|||
key: 'car', |
|||
label: "天气" |
|||
}, |
|||
{ |
|||
key: 'pie', |
|||
label: "天气" |
|||
}, |
|||
] |
|||
} |
|||
}, |
|||
methods: { |
|||
handleClick(item) { |
|||
this.active = item.key |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang='scss' scoped> |
|||
.CrowdnessIndicatorRankings {} |
|||
.border { |
|||
border: 1px solid; |
|||
border-image: linear-gradient(360deg, rgba(55, 231, 255, .42), rgba(55, 231, 255, 0)) 1 1; |
|||
} |
|||
|
|||
.CrowdnessIndicatorRankings { |
|||
::v-deep { |
|||
.content { |
|||
display: flex; |
|||
padding: 0; |
|||
} |
|||
} |
|||
|
|||
.map { |
|||
flex: 1; |
|||
} |
|||
|
|||
.right { |
|||
right: 0px; |
|||
height: 100%; |
|||
top: 0; |
|||
width: 34px; |
|||
background: linear-gradient(180deg, rgba(25, 40, 52, .6) 0%, rgba(28, 50, 60, .6) 100%); |
|||
border-right: 0; |
|||
border-bottom: 0; |
|||
|
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 6px; |
|||
|
|||
.item { |
|||
width: 100%; |
|||
height: 34px; |
|||
padding: 3px; |
|||
cursor: pointer; |
|||
|
|||
div { |
|||
width: 100%; |
|||
height: 100%; |
|||
transition: all .15s linear; |
|||
background-size: 100% 100%; |
|||
background-repeat: no-repeat; |
|||
background-position: center; |
|||
} |
|||
} |
|||
|
|||
.active { |
|||
background: linear-gradient(180deg, #005C79 0%, #009BCC 100%); |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
|
@ -1,20 +1,234 @@ |
|||
<template> |
|||
<Card class='DisposalPlan' title="处置预案"> |
|||
DisposalPlan |
|||
<canvas ref="FlowCanvasRef" /> |
|||
</Card> |
|||
</template> |
|||
|
|||
<script> |
|||
import Card from "./../../components/Card.vue" |
|||
import Card from "./../../components/Card.vue"; |
|||
import { CanvasFlow } from "./utils"; |
|||
|
|||
import { merge } from "lodash" |
|||
|
|||
function getDefaultBlockOption() { |
|||
return { |
|||
width: 100, |
|||
height: 30, |
|||
radius: 9, |
|||
linearGradient: "linear-gradient(180deg, #005C79 0%, #009BCC 100%)", |
|||
text: { |
|||
font: "PingFang SC", |
|||
fontSize: 14, |
|||
color: "#FFFFFF" |
|||
}, |
|||
} |
|||
} |
|||
|
|||
function getDefaultLegendOption() { |
|||
return { |
|||
width: 9, |
|||
height: 9, |
|||
y: 0, |
|||
text: { |
|||
font: "PingFang SC", |
|||
fontSize: 9, |
|||
align: 'left', |
|||
color: "#FFFFFF" |
|||
}, |
|||
} |
|||
} |
|||
|
|||
export default { |
|||
name: 'DisposalPlan', |
|||
components: { |
|||
Card |
|||
}, |
|||
data() { |
|||
return { |
|||
// list: [] |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.canvasFlow = new CanvasFlow(this.$refs.FlowCanvasRef); |
|||
|
|||
this.draw(); |
|||
}, |
|||
methods: { |
|||
drawBlock({ x, y, width, height, backgroundColor, linearGradient, |
|||
radius, |
|||
text: { |
|||
color, text, fontSize, fontWeight, fontFamily |
|||
} = {} }) { |
|||
|
|||
if (linearGradient) this.canvasFlow.setLinearGradient(...this.canvasFlow.transformCssLinearGradient(x, y, width, height, linearGradient)) |
|||
|
|||
this.canvasFlow.drawRectangle({ x, y, width, height, backgroundColor, radius }); |
|||
|
|||
if (text) { |
|||
this.canvasFlow.fillText(text, { x, y, color, fontSize, fontWeight, fontFamily }, { width, height }); |
|||
} |
|||
}, |
|||
drawLegend({ x, y, width, height, backgroundColor, linearGradient, |
|||
radius, |
|||
text: { |
|||
text, ...textOptions |
|||
} = {} }) { |
|||
|
|||
if (linearGradient) this.canvasFlow.setLinearGradient(...this.canvasFlow.transformCssLinearGradient(x, y, width, height, linearGradient)) |
|||
|
|||
this.canvasFlow.drawRectangle({ x, y, width, height, backgroundColor, radius }); |
|||
|
|||
if (text) { |
|||
this.canvasFlow.fillText(text, { ...textOptions, x: x + width + 3, y: y + height / 2, }, { height }); |
|||
} |
|||
}, |
|||
drawLine({ x, y, width, height, backgroundColor, linearGradient, }) { |
|||
|
|||
}, |
|||
draw() { |
|||
const { clientWidth } = this.$refs.FlowCanvasRef; |
|||
|
|||
const defaultBlockOption = getDefaultBlockOption(); |
|||
|
|||
const halfWidth = defaultBlockOption.width / 2; |
|||
|
|||
this.canvasFlow.clear(); |
|||
|
|||
const legends = [ |
|||
{ |
|||
type: 'drawLegend', |
|||
options: merge(getDefaultLegendOption(), { |
|||
x: 150, |
|||
linearGradient: "linear-gradient(180deg, #004960 0%, #004B62 100%)", |
|||
text: { |
|||
text: "未完成" |
|||
} |
|||
}) |
|||
}, |
|||
{ |
|||
type: 'drawLegend', |
|||
options: merge(getDefaultLegendOption(), { |
|||
x: 210, |
|||
linearGradient: "linear-gradient(90deg, #006121 0%, #488000 100%)", |
|||
text: { |
|||
text: "进行中" |
|||
} |
|||
}) |
|||
}, |
|||
{ |
|||
type: 'drawLegend', |
|||
options: merge(getDefaultLegendOption(), { |
|||
x: 270, |
|||
linearGradient: "linear-gradient(180deg, #005C79 0%, #009BCC 100%)", |
|||
text: { |
|||
text: "已完成" |
|||
} |
|||
}) |
|||
} |
|||
] |
|||
|
|||
const blockList = [ |
|||
{ |
|||
type: 'drawBlock', |
|||
key: "DisposalPlan", |
|||
options: merge(getDefaultBlockOption(), { |
|||
x: clientWidth / 2 - halfWidth, |
|||
y: 24, |
|||
text: { |
|||
text: "处置预案" |
|||
} |
|||
}) |
|||
}, |
|||
{ |
|||
type: 'drawBlock', |
|||
key: "AnalyzeConfirm", |
|||
options: merge(getDefaultBlockOption(), { |
|||
x: clientWidth / 2 - halfWidth, |
|||
y: 75, |
|||
text: { |
|||
text: "分析确认" |
|||
} |
|||
}) |
|||
}, |
|||
{ |
|||
type: 'drawBlock', |
|||
key: "InstructionsGiven", |
|||
options: merge(getDefaultBlockOption(), { |
|||
x: clientWidth / 7 - halfWidth, |
|||
y: 123, |
|||
linearGradient: "linear-gradient(90deg, #006121 0%, #488000 100%)", |
|||
text: { |
|||
text: "指令下达" |
|||
} |
|||
}) |
|||
}, |
|||
{ |
|||
type: 'drawBlock', |
|||
key: "OnSiteConfirmation", |
|||
options: merge(getDefaultBlockOption(), { |
|||
x: clientWidth / 7 * 6 - halfWidth, |
|||
y: 123, |
|||
linearGradient: "linear-gradient(90deg, #006121 0%, #488000 100%)", |
|||
text: { |
|||
text: "现场确认" |
|||
} |
|||
}) |
|||
}, |
|||
{ |
|||
type: 'drawBlock', |
|||
key: "OnSiteDisposal", |
|||
options: merge(getDefaultBlockOption(), { |
|||
x: clientWidth / 2 - halfWidth, |
|||
y: 170, |
|||
linearGradient: "linear-gradient(180deg, #004960 0%, #004B62 100%)", |
|||
text: { |
|||
text: "现场处置" |
|||
} |
|||
}) |
|||
}, |
|||
{ |
|||
type: 'drawBlock', |
|||
key: "FollowUpProcessing", |
|||
options: merge(getDefaultBlockOption(), { |
|||
x: clientWidth / 2 - halfWidth, |
|||
y: 225, |
|||
linearGradient: "linear-gradient(180deg, #004960 0%, #004B62 100%)", |
|||
text: { |
|||
text: "后续处理" |
|||
} |
|||
}) |
|||
}, |
|||
]; |
|||
|
|||
const linePoints = [ |
|||
{ |
|||
x: clientWidth / 7 - halfWidth, |
|||
y: 1 |
|||
} |
|||
]; |
|||
|
|||
[...blockList, ...legends].forEach(item => { |
|||
this[item.type]?.(item.options) |
|||
}) |
|||
} |
|||
}, |
|||
|
|||
} |
|||
</script> |
|||
|
|||
<style lang='scss' scoped> |
|||
.DisposalPlan {} |
|||
.DisposalPlan { |
|||
::v-deep { |
|||
.content { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
} |
|||
|
|||
canvas { |
|||
flex: 1; |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
} |
|||
</style> |
|||
|
@ -0,0 +1,202 @@ |
|||
export function getQuadrant(angle) { |
|||
angle %= 360.0; |
|||
if (angle < 0) angle += 360.0; |
|||
var quadrant = Math.floor(angle / 90) + 1; |
|||
return quadrant; |
|||
} |
|||
|
|||
export function calcPoint(x0, y0, width, height, angle) { |
|||
const parseAngle = parseFloat(angle); |
|||
|
|||
angle = (Math.PI / 180) * parseFloat(parseAngle); |
|||
|
|||
x0 += width / 2; |
|||
y0 += height / 2; |
|||
|
|||
const hypotenuse = width / 2 > height / 2 ? width / 2 : height / 2; |
|||
|
|||
// 对边
|
|||
let oppositeEdge = Math.abs(hypotenuse * Math.sin(angle)); |
|||
|
|||
// 邻边
|
|||
let adjacentEdge = Math.abs(hypotenuse * Math.cos(angle)); |
|||
|
|||
let point0 = [], |
|||
point1 = []; |
|||
|
|||
switch (parseFloat(parseAngle) % 360.0) { |
|||
case 180: |
|||
oppositeEdge = adjacentEdge; |
|||
adjacentEdge = 0; |
|||
break; |
|||
case 90: |
|||
case 270: |
|||
adjacentEdge = oppositeEdge; |
|||
oppositeEdge = 0; |
|||
break; |
|||
} |
|||
|
|||
switch (getQuadrant(parseAngle)) { |
|||
case 1: |
|||
point0 = [x0 - adjacentEdge, y0 + oppositeEdge]; |
|||
point1 = [x0 + adjacentEdge, y0 - oppositeEdge]; |
|||
break; |
|||
case 2: |
|||
point0 = [x0 - adjacentEdge, y0 - oppositeEdge]; |
|||
point1 = [x0 + adjacentEdge, y0 + oppositeEdge]; |
|||
break; |
|||
case 3: |
|||
point0 = [x0 + adjacentEdge, y0 - oppositeEdge]; |
|||
point1 = [x0 - adjacentEdge, y0 + oppositeEdge]; |
|||
break; |
|||
case 4: |
|||
point0 = [x0 + adjacentEdge, y0 + oppositeEdge]; |
|||
point1 = [x0 - adjacentEdge, y0 - oppositeEdge]; |
|||
break; |
|||
} |
|||
|
|||
return angle > 0 ? [point0, point1] : [point1, point0]; |
|||
} |
|||
|
|||
export class CanvasFlow { |
|||
canvas = null; |
|||
context = null; |
|||
zoom = 3; |
|||
constructor(canvas) { |
|||
this.canvas = canvas; |
|||
this.context = canvas.getContext("2d"); |
|||
|
|||
this.canvas.width = canvas.clientWidth * this.zoom; |
|||
this.canvas.height = canvas.clientHeight * this.zoom; |
|||
} |
|||
|
|||
drawRectangle({ |
|||
x, |
|||
y, |
|||
width, |
|||
height, |
|||
borderWidth, |
|||
borderColor, |
|||
backgroundColor, |
|||
radius, |
|||
}) { |
|||
if (backgroundColor) this.context.fillStyle = backgroundColor; |
|||
|
|||
x *= this.zoom; |
|||
y *= this.zoom; |
|||
width *= this.zoom; |
|||
height *= this.zoom; |
|||
|
|||
if (radius) { |
|||
this.context.beginPath(); |
|||
this.context.roundRect(x, y, width, height, radius); |
|||
this.context.fill(); |
|||
} else { |
|||
this.context.fillRect(x, y, width, height); |
|||
} |
|||
|
|||
if (typeof borderWidth === "number") { |
|||
borderWidth *= this.zoom; |
|||
|
|||
this.context.lineWidth = borderWidth; |
|||
this.context.strokeStyle = borderColor; |
|||
|
|||
this.context.stroke(); |
|||
} |
|||
} |
|||
|
|||
transformCssLinearGradient(x, y, width, height, linearGradient) { |
|||
const [_, deg, ...stopStrings] = linearGradient.match(/[^,()]+/g); |
|||
|
|||
const stops = stopStrings.map((stop) => { |
|||
const [color, offset] = stop.trim().split(" "); |
|||
|
|||
return { |
|||
color, |
|||
offset: parseFloat(offset) * 0.01, |
|||
}; |
|||
}); |
|||
|
|||
const [point0, point1] = calcPoint(x, y, width, height, deg); |
|||
|
|||
return [ |
|||
{ |
|||
x0: point0[0], |
|||
y0: point0[1], |
|||
x1: point1[0], |
|||
y1: point1[1], |
|||
}, |
|||
stops, |
|||
]; |
|||
} |
|||
|
|||
setLinearGradient({ x0, y0, x1, y1 }, stops) { |
|||
x0 *= this.zoom; |
|||
x1 *= this.zoom; |
|||
y0 *= this.zoom; |
|||
y1 *= this.zoom; |
|||
|
|||
const gradient = this.context.createLinearGradient(x0, y0, x1, y1); |
|||
|
|||
stops.forEach(({ offset, color }) => { |
|||
gradient.addColorStop(offset, color); |
|||
}); |
|||
|
|||
this.context.fillStyle = gradient; |
|||
} |
|||
|
|||
drawLine({ startX, endX, color }, vertices) { |
|||
startX *= this.zoom; |
|||
endX *= this.zoom; |
|||
|
|||
ctx.beginPath(); |
|||
|
|||
ctx.moveTo(startX, endX); |
|||
|
|||
vertices.forEach(({ x, y }) => { |
|||
ctx.lineTo(x * this.zoom, y * this.zoom); |
|||
}); |
|||
|
|||
this.context.strokeStyle = color; |
|||
|
|||
ctx.stroke(); |
|||
} |
|||
|
|||
fillText( |
|||
text, |
|||
{ |
|||
x, |
|||
y, |
|||
fontSize = 15, |
|||
color, |
|||
align = "center", |
|||
fontWeight = "normal", |
|||
fontFamily = "微软雅黑", |
|||
} = {}, |
|||
{ width: rectWidth = 0, height: rectHeight = 0 } = {} |
|||
) { |
|||
this.context.font = `${fontWeight} ${fontSize * this.zoom}px ${fontFamily}`; |
|||
|
|||
this.context.textBaseline = "middle"; |
|||
|
|||
const textWidth = this.context.measureText(text).width; |
|||
|
|||
x *= this.zoom; |
|||
|
|||
switch (align) { |
|||
case "center": |
|||
x += (rectWidth * this.zoom - textWidth) / 2; |
|||
|
|||
y += rectHeight / 2; |
|||
break; |
|||
} |
|||
|
|||
this.context.fillStyle = color; |
|||
|
|||
this.context.fillText(text, x, y * this.zoom); |
|||
} |
|||
|
|||
clear() { |
|||
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); |
|||
} |
|||
} |
Loading…
Reference in new issue