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> |
<template> |
||||
<Card class='CrowdnessIndicatorRankings' title="拥挤度指标排名情况"> |
<Card class='CrowdnessIndicatorRankings border' title="拥挤度指标排名情况"> |
||||
CrowdnessIndicatorRankings |
<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> |
</Card> |
||||
</template> |
</template> |
||||
|
|
||||
<script> |
<script> |
||||
import Card from "./../../components/Card.vue" |
import Card from "./../../components/Card.vue"; |
||||
|
|
||||
|
import Popover from "@screen/components/Popover/index.vue" |
||||
|
|
||||
export default { |
export default { |
||||
name: 'CrowdnessIndicatorRankings', |
name: 'CrowdnessIndicatorRankings', |
||||
components: { |
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> |
</script> |
||||
|
|
||||
<style lang='scss' scoped> |
<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> |
</style> |
||||
|
@ -1,20 +1,234 @@ |
|||||
<template> |
<template> |
||||
<Card class='DisposalPlan' title="处置预案"> |
<Card class='DisposalPlan' title="处置预案"> |
||||
DisposalPlan |
<canvas ref="FlowCanvasRef" /> |
||||
</Card> |
</Card> |
||||
</template> |
</template> |
||||
|
|
||||
<script> |
<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 { |
export default { |
||||
name: 'DisposalPlan', |
name: 'DisposalPlan', |
||||
components: { |
components: { |
||||
Card |
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> |
</script> |
||||
|
|
||||
<style lang='scss' scoped> |
<style lang='scss' scoped> |
||||
.DisposalPlan {} |
.DisposalPlan { |
||||
|
::v-deep { |
||||
|
.content { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
canvas { |
||||
|
flex: 1; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
} |
||||
</style> |
</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