tabulation.vue 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. <template>
  2. <div class="layout" v-if="roadPhoto">
  3. <ui-button @click="downLayoutImage">下载</ui-button>
  4. <ui-button @click="history.undo" type="primary" :class="{disabled: !history.state.hasUndo}">撤销</ui-button>
  5. <ui-button @click="history.redo" type="primary" :class="{disabled: !history.state.hasRedo}">恢复</ui-button>
  6. <ui-button type="primary" @click="roadPhoto.table = history.value">保存</ui-button>
  7. <div class="content" ref="layoutRef">
  8. <h2>{{ roadPhoto.title || "默认名称" }}</h2>
  9. <table>
  10. <tr>
  11. <td class="label" width="150">到达事故现场时间</td>
  12. <td class="value">
  13. <ui-input
  14. type="text"
  15. @input="input"
  16. v-model="history.value.arrivalTime"
  17. @blur="history.push"
  18. />
  19. </td>
  20. <td class="label" width="100">天气</td>
  21. <td class="value" width="80">
  22. <ui-input
  23. type="text"
  24. @input="input"
  25. v-model="history.value.weather"
  26. @blur="history.push"
  27. />
  28. </td>
  29. <td class="label" width="100">路面性质</td>
  30. <td class="value" width="150">
  31. <ui-input
  32. type="text"
  33. @input="input"
  34. v-model="history.value.conditions"
  35. @blur="history.push"
  36. />
  37. </td>
  38. </tr>
  39. <tr>
  40. <td class="label">事故发生地点</td>
  41. <td class="value" colspan="5">
  42. <ui-input
  43. type="text"
  44. @input="input"
  45. v-model="history.value.location"
  46. @blur="history.push"
  47. />
  48. </td>
  49. </tr>
  50. <tr>
  51. <td class="image" colspan="6">
  52. <div>
  53. <img :src="getStaticFile(roadPhoto.photoUrl)" @blur="history.push" class="photo" />
  54. <img
  55. src="/static/compass.png"
  56. :style="{transform: `rotateZ(${history.value.compassAngle}deg)`}"
  57. class="compass"
  58. @mousedown="downHandler"
  59. />
  60. </div>
  61. </td>
  62. </tr>
  63. <tr>
  64. <td class="value" colspan="6">
  65. <ui-input
  66. type="text"
  67. @input="input"
  68. v-model="history.value.illustrate"
  69. @blur="history.push"
  70. />
  71. </td>
  72. </tr>
  73. <tr>
  74. <td class="value" colspan="6">
  75. <ui-input
  76. type="text"
  77. @input="input"
  78. v-model="history.value.other"
  79. @blur="history.push"
  80. />
  81. </td>
  82. </tr>
  83. </table>
  84. <div class="signatures">
  85. <p class="signature">绘图员:</p>
  86. <p class="signature">当事人签名:</p>
  87. <p class="signature">勘察员:</p>
  88. <p class="signature">见证人签名:</p>
  89. </div>
  90. </div>
  91. </div>
  92. </template>
  93. <script setup lang="ts">
  94. import { router, writeRouteName } from '@/router'
  95. import {computed, ref} from "vue";
  96. import { useHistory } from '@/hook/useHistory'
  97. import {roadPhotos, RoadPhoto, getDefaultTable} from "@/store/roadPhotos";
  98. import {getStaticFile} from "@/dbo/main";
  99. import html2canvas from 'html2canvas'
  100. import UiButton from "@/components/base/components/button/index.vue";
  101. import UiInput from "@/components/base/components/input/index.vue";
  102. import { mathUtil } from '@/graphic/Util/MathUtil'
  103. import {getPostionByTarget} from "@/components/base/utils";
  104. const roadPhoto = computed<RoadPhoto>(() => {
  105. const route = router.currentRoute.value;
  106. const params = route.params
  107. let data
  108. if (route.name !== writeRouteName.tabulation) {
  109. return null
  110. } else if (!params.id || !(data = roadPhotos.value.find(data => data.id === params.id))) {
  111. router.back();
  112. return null;
  113. }
  114. return data
  115. })
  116. const history = computed(
  117. () => roadPhoto.value && useHistory(getDefaultTable(roadPhoto.value))
  118. )
  119. const input = () => {
  120. history.value.state.hasRedo = false
  121. }
  122. const downHandler = (ev: MouseEvent) => {
  123. const target = (ev.target as HTMLImageElement)
  124. const page = getPostionByTarget(target, document.documentElement)
  125. const start = { x: page.x + target.offsetWidth / 2, y: page.y }
  126. const center = { x: page.x + target.offsetWidth / 2, y: page.y + target.offsetHeight / 2 }
  127. let angle
  128. const moveHandler = (ev: MouseEvent) => {
  129. const move = {
  130. x: ev.pageX,
  131. y: ev.pageY
  132. }
  133. angle = mathUtil.Angle(center, start, move)
  134. angle = move.x < start.x ? -angle : angle
  135. target.style.transform = `rotateZ(${angle}deg)`
  136. ev.stopPropagation();
  137. ev.preventDefault();
  138. }
  139. const upHandler = (ev:MouseEvent) => {
  140. document.documentElement.removeEventListener("mousemove", moveHandler)
  141. document.documentElement.removeEventListener("mouseup", upHandler);
  142. ev.stopPropagation();
  143. ev.preventDefault();
  144. history.value.value.compassAngle = angle
  145. history.value.push()
  146. }
  147. document.documentElement.addEventListener("mousemove", moveHandler)
  148. document.documentElement.addEventListener("mouseup", upHandler)
  149. ev.stopPropagation();
  150. ev.preventDefault();
  151. }
  152. const layoutRef = ref<HTMLDivElement>()
  153. const downLayoutImage = async () => {
  154. const canvas = await html2canvas(layoutRef.value)
  155. const blob = await new Promise<Blob>(resolve => canvas.toBlob(resolve, "image/jpeg", 0.95))
  156. window.open(URL.createObjectURL(blob))
  157. }
  158. </script>
  159. <style lang="scss" scoped>
  160. .layout {
  161. overflow-y: auto;
  162. height: 100%;
  163. }
  164. .content {
  165. box-sizing: content-box;
  166. width: 980px;
  167. height: 1300px;
  168. padding: 20px;
  169. padding-bottom: 60px;
  170. margin: 0 auto;
  171. }
  172. .image {
  173. position: relative;
  174. div {
  175. position: absolute;
  176. left: 0;
  177. right: 0;
  178. bottom: 0;
  179. top: 0;
  180. .photo {
  181. max-width: 100%;
  182. max-height: 100%;
  183. }
  184. .compass {
  185. position: absolute;
  186. right: 20px;
  187. top: 20px;
  188. width: 60px;
  189. }
  190. }
  191. }
  192. .content table {
  193. width: 980px;
  194. height: 800px;
  195. border: 2px solid #000;
  196. border-collapse: collapse;
  197. td:not(:last-child) {
  198. border-right: 2px solid #000;
  199. }
  200. tr:not(:last-child) td {
  201. border-bottom: 2px solid #000;
  202. }
  203. .value {
  204. height: 43px;
  205. }
  206. }
  207. .signatures {
  208. display: flex;
  209. .signature {
  210. flex: 1;
  211. }
  212. }
  213. </style>
  214. <style lang="scss">
  215. .value {
  216. box-sizing: border-box;
  217. padding: 8px 10px;
  218. input,
  219. .ui-input {
  220. width: 100%;
  221. height: 100%;
  222. padding: 0 !important;
  223. outline: none !important;
  224. border: none !important;
  225. color: #000 !important;
  226. }
  227. }
  228. </style>