|
@@ -1,194 +1,235 @@
|
|
<template>
|
|
<template>
|
|
- <div class="layout" v-if="roadPhoto">
|
|
|
|
- <ui-button @click="downLayoutImage">下载</ui-button>
|
|
|
|
- <ui-button @click="history.undo" type="primary" :class="{disabled: !history.state.hasUndo}">撤销</ui-button>
|
|
|
|
- <ui-button @click="history.redo" type="primary" :class="{disabled: !history.state.hasRedo}">恢复</ui-button>
|
|
|
|
- <ui-button type="primary" @click="roadPhoto.table = history.value">保存</ui-button>
|
|
|
|
- <div class="content" ref="layoutRef">
|
|
|
|
- <h2>{{ roadPhoto.title || "默认名称" }}</h2>
|
|
|
|
- <table>
|
|
|
|
- <tr>
|
|
|
|
- <td class="label" width="150">到达事故现场时间</td>
|
|
|
|
- <td class="value">
|
|
|
|
- <ui-input
|
|
|
|
- type="text"
|
|
|
|
- @input="input"
|
|
|
|
- v-model="history.value.arrivalTime"
|
|
|
|
- @blur="history.push"
|
|
|
|
- />
|
|
|
|
- </td>
|
|
|
|
- <td class="label" width="100">天气</td>
|
|
|
|
- <td class="value" width="80">
|
|
|
|
- <ui-input
|
|
|
|
- type="text"
|
|
|
|
- @input="input"
|
|
|
|
- v-model="history.value.weather"
|
|
|
|
- @blur="history.push"
|
|
|
|
- />
|
|
|
|
- </td>
|
|
|
|
- <td class="label" width="100">路面性质</td>
|
|
|
|
- <td class="value" width="150">
|
|
|
|
- <ui-input
|
|
|
|
- type="text"
|
|
|
|
- @input="input"
|
|
|
|
- v-model="history.value.conditions"
|
|
|
|
- @blur="history.push"
|
|
|
|
- />
|
|
|
|
- </td>
|
|
|
|
- </tr>
|
|
|
|
- <tr>
|
|
|
|
- <td class="label">事故发生地点</td>
|
|
|
|
- <td class="value" colspan="5">
|
|
|
|
- <ui-input
|
|
|
|
- type="text"
|
|
|
|
- @input="input"
|
|
|
|
- v-model="history.value.location"
|
|
|
|
- @blur="history.push"
|
|
|
|
- />
|
|
|
|
- </td>
|
|
|
|
- </tr>
|
|
|
|
- <tr>
|
|
|
|
- <td class="image" colspan="6">
|
|
|
|
- <div>
|
|
|
|
- <img :src="getStaticFile(roadPhoto.photoUrl)" @blur="history.push" class="photo" />
|
|
|
|
- <img
|
|
|
|
- src="/static/compass.png"
|
|
|
|
- :style="{transform: `rotateZ(${history.value.compassAngle}deg)`}"
|
|
|
|
- class="compass"
|
|
|
|
- @mousedown="downHandler"
|
|
|
|
|
|
+ <MainPanel>
|
|
|
|
+ <template v-slot:header>
|
|
|
|
+ <Header title="现场绘图 | 制表">
|
|
|
|
+ <ui-button
|
|
|
|
+ type="primary"
|
|
|
|
+ @click="saveHandler"
|
|
|
|
+ width="96px"
|
|
|
|
+ >
|
|
|
|
+ 保存
|
|
|
|
+ </ui-button>
|
|
|
|
+ </Header>
|
|
|
|
+ </template>
|
|
|
|
+
|
|
|
|
+ <div class="tab-layout" v-if="roadPhoto">
|
|
|
|
+ <div class="content" ref="layoutRef">
|
|
|
|
+ <table>
|
|
|
|
+ <tr>
|
|
|
|
+ <td class="value title" colspan="6" height="64">
|
|
|
|
+ <span v-if="downMode">{{roadPhoto.title}}</span>
|
|
|
|
+ <ui-input
|
|
|
|
+ v-else
|
|
|
|
+ type="text"
|
|
|
|
+ @input="input"
|
|
|
|
+ v-model="roadPhoto.title"
|
|
|
|
+ @blur="history.push"
|
|
|
|
+ />
|
|
|
|
+ </td>
|
|
|
|
+ </tr>
|
|
|
|
+ <tr>
|
|
|
|
+ <td class="label" width="150" height="64">到达事故现场时间</td>
|
|
|
|
+ <td class="value">
|
|
|
|
+ <span v-if="downMode">{{history.value.arrivalTime}}</span>
|
|
|
|
+ <ui-input
|
|
|
|
+ v-else
|
|
|
|
+ type="text"
|
|
|
|
+ @input="input"
|
|
|
|
+ v-model="history.value.arrivalTime"
|
|
|
|
+ @blur="history.push"
|
|
|
|
+ />
|
|
|
|
+ </td>
|
|
|
|
+ <td class="label" width="100">天气</td>
|
|
|
|
+ <td class="value" width="80">
|
|
|
|
+ <span v-if="downMode">{{history.value.weather}}</span>
|
|
|
|
+ <ui-input
|
|
|
|
+ v-else
|
|
|
|
+ type="text"
|
|
|
|
+ @input="input"
|
|
|
|
+ v-model="history.value.weather"
|
|
|
|
+ @blur="history.push"
|
|
|
|
+ />
|
|
|
|
+ </td>
|
|
|
|
+ <td class="label" width="100">路面性质</td>
|
|
|
|
+ <td class="value" width="150">
|
|
|
|
+ <span v-if="downMode">{{history.value.conditions}}</span>
|
|
|
|
+ <ui-input
|
|
|
|
+ v-else
|
|
|
|
+ type="text"
|
|
|
|
+ @input="input"
|
|
|
|
+ v-model="history.value.conditions"
|
|
|
|
+ @blur="history.push"
|
|
|
|
+ />
|
|
|
|
+ </td>
|
|
|
|
+ </tr>
|
|
|
|
+ <tr>
|
|
|
|
+ <td class="label" height="64">事故发生地点</td>
|
|
|
|
+ <td class="value" colspan="5">
|
|
|
|
+ <span v-if="downMode">{{history.value.location}}</span>
|
|
|
|
+ <ui-input
|
|
|
|
+ v-else
|
|
|
|
+ type="text"
|
|
|
|
+ @input="input"
|
|
|
|
+ v-model="history.value.location"
|
|
|
|
+ @blur="history.push"
|
|
/>
|
|
/>
|
|
- </div>
|
|
|
|
- </td>
|
|
|
|
- </tr>
|
|
|
|
- <tr>
|
|
|
|
- <td class="value" colspan="6">
|
|
|
|
- <ui-input
|
|
|
|
- type="text"
|
|
|
|
- @input="input"
|
|
|
|
- v-model="history.value.illustrate"
|
|
|
|
- @blur="history.push"
|
|
|
|
- />
|
|
|
|
- </td>
|
|
|
|
- </tr>
|
|
|
|
- <tr>
|
|
|
|
- <td class="value" colspan="6">
|
|
|
|
- <ui-input
|
|
|
|
- type="text"
|
|
|
|
- @input="input"
|
|
|
|
- v-model="history.value.other"
|
|
|
|
- @blur="history.push"
|
|
|
|
- />
|
|
|
|
- </td>
|
|
|
|
- </tr>
|
|
|
|
- </table>
|
|
|
|
- <div class="signatures">
|
|
|
|
- <p class="signature">绘图员:</p>
|
|
|
|
- <p class="signature">当事人签名:</p>
|
|
|
|
- <p class="signature">勘察员:</p>
|
|
|
|
- <p class="signature">见证人签名:</p>
|
|
|
|
|
|
+ </td>
|
|
|
|
+ </tr>
|
|
|
|
+ <tr>
|
|
|
|
+ <td class="image" colspan="6" height="360">
|
|
|
|
+ <div class="photo-layout">
|
|
|
|
+ <img
|
|
|
|
+ :src="getStaticFile(roadPhoto.photoUrl)"
|
|
|
|
+ @blur="history.push"
|
|
|
|
+ class="photo"
|
|
|
|
+ :style="{transform: photoCSSMatrix}"
|
|
|
|
+ ref="photoRef"
|
|
|
|
+ />
|
|
|
|
+ <img
|
|
|
|
+ src="/static/compass.png"
|
|
|
|
+ class="compass"
|
|
|
|
+ :style="{transform: compassCSSMatrix}"
|
|
|
|
+ ref="compassRef"
|
|
|
|
+ />
|
|
|
|
+<!-- :style="{transform: `rotateZ(${history.value.compassAngle}deg)`}"-->
|
|
|
|
+<!-- @mousedown="downHandler"-->
|
|
|
|
+ </div>
|
|
|
|
+ </td>
|
|
|
|
+ </tr>
|
|
|
|
+ <tr>
|
|
|
|
+ <td class="value" colspan="6" height="64">
|
|
|
|
+ <span v-if="downMode">{{history.value.illustrate}}</span>
|
|
|
|
+ <ui-input
|
|
|
|
+ v-else
|
|
|
|
+ type="text"
|
|
|
|
+ @input="input"
|
|
|
|
+ v-model="history.value.illustrate"
|
|
|
|
+ @blur="history.push"
|
|
|
|
+ />
|
|
|
|
+ </td>
|
|
|
|
+ </tr>
|
|
|
|
+ <tr >
|
|
|
|
+ <td class="value date" colspan="6" height="48">
|
|
|
|
+ {{ formatDate(new Date(), "yyyy年MM月dd日hh时mm分") }}
|
|
|
|
+ </td>
|
|
|
|
+ </tr>
|
|
|
|
+ </table>
|
|
|
|
+ <div class="signatures">
|
|
|
|
+ <p class="signature">绘图员:</p>
|
|
|
|
+ <p class="signature">当事人签名:</p>
|
|
|
|
+ <p class="signature">勘察员:</p>
|
|
|
|
+ <p class="signature">见证人签名:</p>
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
- </div>
|
|
|
|
|
|
+ </MainPanel>
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
import { router, writeRouteName } from '@/router'
|
|
import { router, writeRouteName } from '@/router'
|
|
-import {computed, ref} from "vue";
|
|
|
|
|
|
+import { formatDate } from "@/utils";
|
|
|
|
+import {computed, nextTick, ref} from "vue";
|
|
import { useHistory } from '@/hook/useHistory'
|
|
import { useHistory } from '@/hook/useHistory'
|
|
import {roadPhotos, RoadPhoto, getDefaultTable} from "@/store/roadPhotos";
|
|
import {roadPhotos, RoadPhoto, getDefaultTable} from "@/store/roadPhotos";
|
|
import {getStaticFile} from "@/dbo/main";
|
|
import {getStaticFile} from "@/dbo/main";
|
|
import html2canvas from 'html2canvas'
|
|
import html2canvas from 'html2canvas'
|
|
import UiButton from "@/components/base/components/button/index.vue";
|
|
import UiButton from "@/components/base/components/button/index.vue";
|
|
import UiInput from "@/components/base/components/input/index.vue";
|
|
import UiInput from "@/components/base/components/input/index.vue";
|
|
-import { mathUtil } from '@/graphic/Util/MathUtil'
|
|
|
|
-import {getPostionByTarget} from "@/components/base/utils";
|
|
|
|
|
|
+import {HandMode, useHand} from '@/hook/useHand'
|
|
|
|
+import Header from "@/components/photos/header.vue";
|
|
|
|
+import MainPanel from "@/components/main-panel/index.vue";
|
|
|
|
|
|
const roadPhoto = computed<RoadPhoto>(() => {
|
|
const roadPhoto = computed<RoadPhoto>(() => {
|
|
- const route = router.currentRoute.value;
|
|
|
|
- const params = route.params
|
|
|
|
- let data
|
|
|
|
- if (route.name !== writeRouteName.tabulation) {
|
|
|
|
- return null
|
|
|
|
- } else if (!params.id || !(data = roadPhotos.value.find(data => data.id === params.id))) {
|
|
|
|
|
|
+ let route, params, data
|
|
|
|
+ if (((route = router.currentRoute.value).name === writeRouteName.tabulation)
|
|
|
|
+ && (params = route.params).id
|
|
|
|
+ && (data = roadPhotos.value.find(data => data.id === params.id))) {
|
|
|
|
+ return data
|
|
|
|
+ } else {
|
|
router.back();
|
|
router.back();
|
|
- return null;
|
|
|
|
}
|
|
}
|
|
- return data
|
|
|
|
})
|
|
})
|
|
const history = computed(
|
|
const history = computed(
|
|
() => roadPhoto.value && useHistory(getDefaultTable(roadPhoto.value))
|
|
() => roadPhoto.value && useHistory(getDefaultTable(roadPhoto.value))
|
|
)
|
|
)
|
|
|
|
+const input = () => history.value.state.hasRedo = false
|
|
|
|
|
|
-const input = () => {
|
|
|
|
- history.value.state.hasRedo = false
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-const downHandler = (ev: MouseEvent) => {
|
|
|
|
- const target = (ev.target as HTMLImageElement)
|
|
|
|
- const page = getPostionByTarget(target, document.documentElement)
|
|
|
|
- const start = { x: page.x + target.offsetWidth / 2, y: page.y }
|
|
|
|
- const center = { x: page.x + target.offsetWidth / 2, y: page.y + target.offsetHeight / 2 }
|
|
|
|
- let angle
|
|
|
|
- const moveHandler = (ev: MouseEvent) => {
|
|
|
|
- const move = {
|
|
|
|
- x: ev.pageX,
|
|
|
|
- y: ev.pageY
|
|
|
|
- }
|
|
|
|
- angle = mathUtil.Angle(center, start, move)
|
|
|
|
- angle = move.x < start.x ? -angle : angle
|
|
|
|
- target.style.transform = `rotateZ(${angle}deg)`
|
|
|
|
- ev.stopPropagation();
|
|
|
|
- ev.preventDefault();
|
|
|
|
- }
|
|
|
|
- const upHandler = (ev:MouseEvent) => {
|
|
|
|
- document.documentElement.removeEventListener("mousemove", moveHandler)
|
|
|
|
- document.documentElement.removeEventListener("mouseup", upHandler);
|
|
|
|
- ev.stopPropagation();
|
|
|
|
- ev.preventDefault();
|
|
|
|
- history.value.value.compassAngle = angle
|
|
|
|
|
|
+const compassRef = ref<HTMLImageElement>()
|
|
|
|
+const { cssMatrix: compassCSSMatrix, matrix: compassMatrix } = useHand(
|
|
|
|
+ compassRef,
|
|
|
|
+ HandMode.Angle,
|
|
|
|
+ () => {
|
|
|
|
+ history.value.value.compassAngle = compassMatrix.value
|
|
history.value.push()
|
|
history.value.push()
|
|
- }
|
|
|
|
- document.documentElement.addEventListener("mousemove", moveHandler)
|
|
|
|
- document.documentElement.addEventListener("mouseup", upHandler)
|
|
|
|
|
|
+ },
|
|
|
|
+ history.value.value.compassAngle
|
|
|
|
+)
|
|
|
|
+const photoRef = ref<HTMLImageElement>()
|
|
|
|
+const { cssMatrix: photoCSSMatrix, matrix: photoMatrix } = useHand(
|
|
|
|
+ photoRef,
|
|
|
|
+ HandMode.MoveAndScale,
|
|
|
|
+ () => {
|
|
|
|
+ history.value.value.imageTransform = photoMatrix.value
|
|
|
|
+ history.value.push()
|
|
|
|
+ },
|
|
|
|
+ history.value.value.imageTransform
|
|
|
|
+)
|
|
|
|
|
|
- ev.stopPropagation();
|
|
|
|
- ev.preventDefault();
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+const downMode = ref(false)
|
|
const layoutRef = ref<HTMLDivElement>()
|
|
const layoutRef = ref<HTMLDivElement>()
|
|
const downLayoutImage = async () => {
|
|
const downLayoutImage = async () => {
|
|
|
|
+ downMode.value = true
|
|
|
|
+ await nextTick()
|
|
const canvas = await html2canvas(layoutRef.value)
|
|
const canvas = await html2canvas(layoutRef.value)
|
|
|
|
+ downMode.value = false
|
|
const blob = await new Promise<Blob>(resolve => canvas.toBlob(resolve, "image/jpeg", 0.95))
|
|
const blob = await new Promise<Blob>(resolve => canvas.toBlob(resolve, "image/jpeg", 0.95))
|
|
window.open(URL.createObjectURL(blob))
|
|
window.open(URL.createObjectURL(blob))
|
|
}
|
|
}
|
|
|
|
+const saveHandler = () => {
|
|
|
|
+ roadPhoto.value.table = history.value.value
|
|
|
|
+ downLayoutImage()
|
|
|
|
+}
|
|
|
|
+
|
|
</script>
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
<style lang="scss" scoped>
|
|
-.layout {
|
|
|
|
|
|
+.tab-layout {
|
|
|
|
+ position: absolute;
|
|
|
|
+ top: calc(var(--header-top) + var(--editor-head-height));
|
|
|
|
+ bottom: 0;
|
|
overflow-y: auto;
|
|
overflow-y: auto;
|
|
- height: 100%;
|
|
|
|
|
|
+ left: 0;
|
|
|
|
+ right: 0;
|
|
|
|
+ color: #000;
|
|
|
|
+ font-size: 16px;
|
|
}
|
|
}
|
|
|
|
|
|
.content {
|
|
.content {
|
|
box-sizing: content-box;
|
|
box-sizing: content-box;
|
|
width: 980px;
|
|
width: 980px;
|
|
- height: 1300px;
|
|
|
|
- padding: 20px;
|
|
|
|
- padding-bottom: 60px;
|
|
|
|
|
|
+ padding: 20px 20px 60px;
|
|
margin: 0 auto;
|
|
margin: 0 auto;
|
|
}
|
|
}
|
|
|
|
|
|
.image {
|
|
.image {
|
|
position: relative;
|
|
position: relative;
|
|
- div {
|
|
|
|
|
|
+ .photo-layout {
|
|
position: absolute;
|
|
position: absolute;
|
|
- left: 0;
|
|
|
|
- right: 0;
|
|
|
|
- bottom: 0;
|
|
|
|
- top: 0;
|
|
|
|
|
|
+ left: 1px;
|
|
|
|
+ right: 1px;
|
|
|
|
+ bottom: 1px;
|
|
|
|
+ top: 1px;
|
|
|
|
+ overflow: hidden;
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: center;
|
|
|
|
+
|
|
.photo {
|
|
.photo {
|
|
max-width: 100%;
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
max-height: 100%;
|
|
|
|
+ align-items: center;
|
|
}
|
|
}
|
|
.compass {
|
|
.compass {
|
|
position: absolute;
|
|
position: absolute;
|
|
@@ -202,22 +243,54 @@ const downLayoutImage = async () => {
|
|
.content table {
|
|
.content table {
|
|
width: 980px;
|
|
width: 980px;
|
|
height: 800px;
|
|
height: 800px;
|
|
- border: 2px solid #000;
|
|
|
|
border-collapse: collapse;
|
|
border-collapse: collapse;
|
|
|
|
|
|
- td:not(:last-child) {
|
|
|
|
- border-right: 2px solid #000;
|
|
|
|
|
|
+
|
|
|
|
+ tr:not(:first-child) {
|
|
|
|
+ &:nth-child(2) td {
|
|
|
|
+ border-top: 2px solid #000;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ td:first-child {
|
|
|
|
+ border-left: 2px solid #000;
|
|
|
|
+ }
|
|
|
|
+ td {
|
|
|
|
+ border-right: 2px solid #000;
|
|
|
|
+ border-bottom: 2px solid #000;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- tr:not(:last-child) td {
|
|
|
|
- border-bottom: 2px solid #000;
|
|
|
|
|
|
+
|
|
|
|
+ .label {
|
|
|
|
+ text-align: center;
|
|
}
|
|
}
|
|
|
|
|
|
.value {
|
|
.value {
|
|
height: 43px;
|
|
height: 43px;
|
|
|
|
+ background-color: #D4E8FF;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .title {
|
|
|
|
+ padding-bottom: 16px;
|
|
|
|
+ position: relative;
|
|
|
|
+
|
|
|
|
+ &:after {
|
|
|
|
+ content: "";
|
|
|
|
+ position: absolute;
|
|
|
|
+ bottom: 0;
|
|
|
|
+ left: 0;
|
|
|
|
+ right: 0;
|
|
|
|
+ height: 8px;
|
|
|
|
+ background-color: #fff;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .date {
|
|
|
|
+ text-align: right;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
.signatures {
|
|
.signatures {
|
|
|
|
+ margin-top: 13px;
|
|
display: flex;
|
|
display: flex;
|
|
|
|
|
|
.signature {
|
|
.signature {
|
|
@@ -233,11 +306,32 @@ const downLayoutImage = async () => {
|
|
input,
|
|
input,
|
|
.ui-input {
|
|
.ui-input {
|
|
width: 100%;
|
|
width: 100%;
|
|
- height: 100%;
|
|
|
|
- padding: 0 !important;
|
|
|
|
|
|
+ height: 32px !important;
|
|
outline: none !important;
|
|
outline: none !important;
|
|
- border: none !important;
|
|
|
|
color: #000 !important;
|
|
color: #000 !important;
|
|
|
|
+ border: 1px #000 dotted !important;
|
|
|
|
+ font-size: 16px !important;
|
|
|
|
+ line-height: 32px !important;
|
|
|
|
+ vertical-align: middle !important;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.title {
|
|
|
|
+ span {
|
|
|
|
+ display: block;
|
|
|
|
+ font-size: 32px !important;
|
|
|
|
+ height: 48px !important;
|
|
|
|
+ font-weight: bold;
|
|
|
|
+ text-align: center;
|
|
|
|
+ line-height: 48px !important;
|
|
|
|
+ }
|
|
|
|
+ input,
|
|
|
|
+ .ui-input {
|
|
|
|
+ font-size: 32px !important;
|
|
|
|
+ height: 48px !important;
|
|
|
|
+ font-weight: bold;
|
|
|
|
+ text-align: center;
|
|
|
|
+ line-height: 48px !important;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
</style>
|