tabulation.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. <template>
  2. <MainPanel>
  3. <template v-slot:header>
  4. <Header title="现场绘图 | 制表" :on-back="onBack" type="return">
  5. <ui-button type="primary" @click="saveHandler" width="96px"> 保存 </ui-button>
  6. </Header>
  7. </template>
  8. <div class="tab-layout" v-if="roadPhoto" :class="{ downMode }">
  9. <div class="content" ref="layoutRef">
  10. <table>
  11. <tr>
  12. <td class="value title" colspan="6" height="62">
  13. <span v-if="downMode">{{ roadPhoto.title }}</span>
  14. <ui-input
  15. v-else
  16. type="text"
  17. @input="input"
  18. v-model="roadPhoto.title"
  19. @blur="history.push"
  20. />
  21. </td>
  22. </tr>
  23. <tr>
  24. <td class="label" width="150" height="50">到达事故现场时间</td>
  25. <td class="value">
  26. <span v-if="downMode">{{ history.value.arrivalTime }}</span>
  27. <ui-input
  28. v-else
  29. type="text"
  30. @input="input"
  31. v-model="history.value.arrivalTime"
  32. @blur="history.push"
  33. />
  34. </td>
  35. <td class="label" width="100">天气</td>
  36. <td class="value" width="80">
  37. <span v-if="downMode">{{ history.value.weather }}</span>
  38. <ui-input
  39. v-else
  40. type="text"
  41. @input="input"
  42. v-model="history.value.weather"
  43. @blur="history.push"
  44. />
  45. </td>
  46. <td class="label" width="100">路面性质</td>
  47. <td class="value" width="150">
  48. <span v-if="downMode">{{ history.value.conditions }}</span>
  49. <ui-input
  50. v-else
  51. type="text"
  52. @input="input"
  53. v-model="history.value.conditions"
  54. @blur="history.push"
  55. />
  56. </td>
  57. </tr>
  58. <tr>
  59. <td class="label" height="40">事故发生地点</td>
  60. <td class="value" colspan="5">
  61. <span v-if="downMode">{{ history.value.location }}</span>
  62. <ui-input
  63. v-else
  64. type="text"
  65. @input="input"
  66. v-model="history.value.location"
  67. @blur="history.push"
  68. />
  69. </td>
  70. </tr>
  71. <tr>
  72. <td class="image" colspan="6" height="676">
  73. <div class="photo-layout">
  74. <img
  75. :src="useStaticUrl(roadPhoto.url).value"
  76. @blur="history.push"
  77. class="photo"
  78. :style="{ transform: photoCSSMatrix }"
  79. ref="photoRef"
  80. />
  81. <img
  82. src="/static/compass.png"
  83. class="compass"
  84. :style="{ transform: compassCSSMatrix }"
  85. ref="compassRef"
  86. />
  87. <p class="compass-info">比例1 : {{ proportion }}</p>
  88. </div>
  89. </td>
  90. </tr>
  91. <tr>
  92. <td class="value textarea-layout" colspan="6" height="73">
  93. <span v-if="downMode">{{ history.value.illustrate }}</span>
  94. <ui-input
  95. class="textarea"
  96. v-else
  97. type="textarea"
  98. @input="input"
  99. v-model="history.value.illustrate"
  100. @blur="history.push"
  101. :maxlength="120"
  102. />
  103. </td>
  104. </tr>
  105. <tr>
  106. <td class="value date" colspan="6" height="50">
  107. {{ formatDate(new Date(), "yyyy年MM月dd日hh时mm分") }}
  108. </td>
  109. </tr>
  110. </table>
  111. <div class="signatures">
  112. <p class="signature">绘图员:</p>
  113. <p class="signature">当事人签名:</p>
  114. <p class="signature">勘察员:</p>
  115. <p class="signature">见证人签名:</p>
  116. </div>
  117. </div>
  118. </div>
  119. </MainPanel>
  120. </template>
  121. <script setup lang="ts">
  122. import { router, writeRouteName } from "@/router";
  123. import { formatDate } from "@/utils";
  124. import { computed, nextTick, onDeactivated, ref, watchEffect } from "vue";
  125. import { useHistory } from "@/hook/useHistory";
  126. import { roadPhotos, RoadPhoto, getDefaultTable } from "@/store/roadPhotos";
  127. import { useStaticUrl } from "@/hook/useStaticUrl";
  128. import html2canvas from "html2canvas";
  129. import UiButton from "@/components/base/components/button/index.vue";
  130. import UiInput from "@/components/base/components/input/index.vue";
  131. import { HandMode, useHand } from "@/hook/useHand";
  132. import Header from "@/components/photos/header.vue";
  133. import MainPanel from "@/components/main-panel/index.vue";
  134. import { downloadImage, uploadImage } from "@/store/sync";
  135. import { Mode } from "@/views/graphic/menus";
  136. import Message from "@/components/base/components/message/message.vue";
  137. const roadPhoto = computed<RoadPhoto>(() => {
  138. let route, params, data;
  139. if (
  140. (route = router.currentRoute.value).name === writeRouteName.tabulation &&
  141. (params = route.params).id &&
  142. (data = roadPhotos.value.find((data) => data.id === params.id))
  143. ) {
  144. return data;
  145. } else {
  146. // router.back();
  147. }
  148. });
  149. const history = computed(
  150. () => roadPhoto.value && useHistory(getDefaultTable(roadPhoto.value))
  151. );
  152. const input = () => (history.value.state.hasRedo = false);
  153. const compassRef = ref<HTMLImageElement>();
  154. const { cssMatrix: compassCSSMatrix, matrix: compassMatrix } = useHand(
  155. compassRef,
  156. HandMode.Angle,
  157. () => {
  158. history.value.value.compassAngle = compassMatrix.value;
  159. history.value.push();
  160. },
  161. history.value.value.compassAngle
  162. );
  163. const photoRef = ref<HTMLImageElement>();
  164. const { cssMatrix: photoCSSMatrix, matrix: photoMatrix } = useHand(
  165. photoRef,
  166. HandMode.MoveAndScale,
  167. () => {
  168. history.value.value.imageTransform = photoMatrix.value;
  169. history.value.push();
  170. },
  171. history.value.value.imageTransform
  172. );
  173. onDeactivated(() => (photoLoaded.value = false));
  174. const proportion = ref(1);
  175. const photoLoaded = ref(false);
  176. watchEffect(() => {
  177. if (!roadPhoto.value || !photoRef.value) {
  178. return;
  179. }
  180. if (!photoLoaded.value) {
  181. photoRef.value.onload = () => (photoLoaded.value = true);
  182. return;
  183. }
  184. const scale = roadPhoto.value.data.scale / window.devicePixelRatio || 1;
  185. const martrixScale = photoMatrix.value[0];
  186. const photoWidth = photoRef.value.naturalWidth;
  187. const prop = ((photoWidth / photoRef.value.offsetWidth) * scale) / martrixScale;
  188. proportion.value = Math.ceil(1 / prop);
  189. });
  190. const onBack = () => {
  191. router.replace({
  192. name: writeRouteName.graphic,
  193. params: { mode: Mode.Road, id: roadPhoto.value.id, action: "update" },
  194. });
  195. };
  196. const downMode = ref(false);
  197. // const downMode = ref(true);
  198. const layoutRef = ref<HTMLDivElement>();
  199. const getLayoutImage = async () => {
  200. downMode.value = true;
  201. await nextTick();
  202. const canvas = await html2canvas(layoutRef.value);
  203. Message.success({ msg: "已保存至相册", time: 2000 });
  204. downMode.value = false;
  205. const blob = await new Promise<Blob>((resolve) =>
  206. canvas.toBlob(resolve, "image/jpeg", 0.95)
  207. );
  208. await downloadImage(blob);
  209. return await uploadImage(blob);
  210. };
  211. const saveHandler = async () => {
  212. roadPhoto.value.table = {
  213. ...history.value.value,
  214. url: await getLayoutImage(),
  215. };
  216. router.replace({ name: writeRouteName.roads });
  217. };
  218. </script>
  219. <style lang="scss" scoped>
  220. .tab-layout {
  221. position: absolute;
  222. top: calc(var(--header-top) + var(--editor-head-height));
  223. bottom: 0;
  224. overflow-y: auto;
  225. left: 0;
  226. right: 0;
  227. font-family: SimSun-Regular, SimSun;
  228. color: #000;
  229. font-size: 16px;
  230. }
  231. .content {
  232. width: 100%;
  233. padding: 20px 20px 60px;
  234. margin: 0 auto;
  235. }
  236. .image {
  237. position: relative;
  238. .photo-layout {
  239. position: absolute;
  240. left: 1px;
  241. right: 1px;
  242. bottom: 1px;
  243. top: 1px;
  244. overflow: hidden;
  245. display: flex;
  246. justify-content: center;
  247. .photo {
  248. max-width: 100%;
  249. max-height: 100%;
  250. align-items: center;
  251. }
  252. .compass {
  253. top: 20px;
  254. }
  255. .compass,
  256. .compass-info {
  257. position: absolute;
  258. right: 20px;
  259. width: 128px;
  260. height: 128px;
  261. top: 20px;
  262. }
  263. .compass-info {
  264. text-align: center;
  265. color: #000;
  266. font-size: 16px;
  267. margin-top: 128px;
  268. pointer-events: none;
  269. white-space: nowrap;
  270. }
  271. }
  272. }
  273. .content table {
  274. width: 100%;
  275. // height: 800px;
  276. border-collapse: collapse;
  277. tr:not(:first-child) {
  278. &:nth-child(2) td {
  279. border-top: 2px solid #000;
  280. }
  281. td:first-child {
  282. border-left: 2px solid #000;
  283. }
  284. td {
  285. border-right: 2px solid #000;
  286. border-bottom: 2px solid #000;
  287. }
  288. }
  289. .label {
  290. text-align: center;
  291. }
  292. .value {
  293. height: 43px;
  294. background-color: #d4e8ff;
  295. }
  296. .title {
  297. padding-bottom: 7px;
  298. position: relative;
  299. &:after {
  300. content: "";
  301. position: absolute;
  302. bottom: 0;
  303. left: 0;
  304. right: 0;
  305. height: 8px;
  306. background-color: #fff;
  307. }
  308. }
  309. .date {
  310. text-align: right;
  311. }
  312. }
  313. .downMode {
  314. .title {
  315. span {
  316. height: 62px !important;
  317. font-size: 54px !important;
  318. font-family: SimSun-Regular, SimSun;
  319. font-weight: 400;
  320. color: #000000;
  321. line-height: 63px;
  322. letter-spacing: 13px;
  323. }
  324. }
  325. .content {
  326. width: 1485px;
  327. height: 1050px;
  328. // padding: 125px 100px 75px 100px;
  329. padding: 30px 28px 26px;
  330. overflow: hidden;
  331. }
  332. .content table .textarea-layout {
  333. height: 73px;
  334. span {
  335. padding: 10px 0px;
  336. display: block;
  337. height: 100%;
  338. }
  339. }
  340. .content table {
  341. .value {
  342. background: none;
  343. }
  344. tr:not(:first-child) {
  345. td {
  346. border-right: 3px solid #000;
  347. border-bottom: 3px solid #000;
  348. }
  349. &:nth-child(2) td {
  350. border-top: 3px solid #000;
  351. }
  352. td:first-child {
  353. border-left: 3px solid #000;
  354. }
  355. td:last-child {
  356. border-right: 3px solid #000;
  357. }
  358. &:last-child td {
  359. border-bottom: 3px solid #000;
  360. }
  361. }
  362. }
  363. }
  364. .signatures {
  365. margin-top: 13px;
  366. display: flex;
  367. .signature {
  368. flex: 1;
  369. }
  370. }
  371. </style>
  372. <style lang="scss">
  373. .value {
  374. box-sizing: border-box;
  375. // padding: 8px 10px;
  376. padding: 0 10px;
  377. input,
  378. .ui-input {
  379. width: 100%;
  380. height: 32px !important;
  381. outline: none !important;
  382. color: #000 !important;
  383. border: 1px #000 dotted !important;
  384. font-size: 16px !important;
  385. line-height: 32px !important;
  386. vertical-align: middle !important;
  387. }
  388. }
  389. .title {
  390. span {
  391. display: block;
  392. font-size: 32px !important;
  393. height: 48px !important;
  394. font-weight: 400;
  395. text-align: center;
  396. line-height: 48px !important;
  397. }
  398. input,
  399. .ui-input {
  400. font-size: 32px !important;
  401. height: 48px !important;
  402. font-weight: bold;
  403. text-align: center;
  404. line-height: 48px !important;
  405. }
  406. }
  407. .textarea {
  408. textarea {
  409. color: #000 !important;
  410. font-size: 16px !important;
  411. line-height: 1.2em;
  412. }
  413. }
  414. .textarea-layout {
  415. .textarea {
  416. height: 100% !important;
  417. }
  418. .retouch {
  419. justify-content: flex-end !important;
  420. .len {
  421. color: #000 !important;
  422. }
  423. }
  424. }
  425. </style>