Image.vue 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. <template>
  2. <div class="title cl">
  3. <span class="y"><router-link to="/home">返回考试主页</router-link></span>
  4. <h2>图片下载</h2>
  5. </div>
  6. <div class="picture cl">
  7. <table cellpadding="0" cellspacing="0" width="100%">
  8. <tr>
  9. <th>下载内容:</th>
  10. <td>
  11. <div class="input-radio">
  12. <a-radio-group name="radioGroup" v-model:value="type">
  13. <a-radio value="1">考生原图</a-radio>
  14. <a-radio value="2">签到表图片</a-radio>
  15. </a-radio-group>
  16. </div>
  17. </td>
  18. </tr>
  19. <tr>
  20. <th>本地保存地址:</th>
  21. <td>
  22. <input
  23. type="text"
  24. style="width: 400px"
  25. class="filetext"
  26. :value="dir"
  27. />
  28. <a href="##" class="filebtn" @click="chooseDirectory">选择</a>
  29. </td>
  30. </tr>
  31. <tr>
  32. <th>图片转存规则:</th>
  33. <td>
  34. <a-input v-model:value="template" style="width: 600px" />
  35. </td>
  36. </tr>
  37. <tr id="message-tr">
  38. <th></th>
  39. <td>
  40. <p v-if="ruleExample" class="error-tetx" id="message-text">
  41. {{ ruleExample }}
  42. </p>
  43. </td>
  44. </tr>
  45. <tr id="append-select">
  46. <th>是否续传:</th>
  47. <td>
  48. <div class="input-radio">
  49. <a-radio-group name="radioGroup" v-model:value="append">
  50. <a-radio :value="true">是</a-radio>
  51. <a-radio :value="false">否</a-radio>
  52. </a-radio-group>
  53. </div>
  54. </td>
  55. </tr>
  56. <tr id="exception-select">
  57. <th>异常处理:</th>
  58. <td>
  59. <div class="input-radio">
  60. <a-radio-group name="radioGroup" v-model:value="failover">
  61. <a-radio :value="true">终止</a-radio>
  62. <a-radio :value="false">跳过</a-radio>
  63. </a-radio-group>
  64. </div>
  65. </td>
  66. </tr>
  67. <template v-if="type === '1'">
  68. <tr id="watermark-select">
  69. <th>添加分数水印:</th>
  70. <td>
  71. <div class="input-radio">
  72. <a-radio-group name="radioGroup" v-model:value="watermark">
  73. <a-radio :value="true">是</a-radio>
  74. <a-radio :value="false">否</a-radio>
  75. </a-radio-group>
  76. </div>
  77. </td>
  78. </tr>
  79. <tr id="track-mode-select">
  80. <th>水印模式:</th>
  81. <td>
  82. <div class="input-radio">
  83. <a-radio-group name="radioGroup" v-model:value="trackMode">
  84. <a-radio value="1">普通</a-radio>
  85. <a-radio value="2">研究生</a-radio>
  86. </a-radio-group>
  87. </div>
  88. </td>
  89. </tr>
  90. <tr>
  91. <th>水印显示区域:</th>
  92. <td>
  93. <a-button type="primary" @click="showPreview">预览</a-button>
  94. </td>
  95. </tr>
  96. <tr id="examNumber-select">
  97. <th>准考证号:</th>
  98. <td>
  99. <a-input
  100. v-model:value="examNumber"
  101. style="width: 600px"
  102. placeholder="多个准考证号用逗号分隔"
  103. />
  104. </td>
  105. </tr>
  106. <tr id="subjectCode-select">
  107. <th>科目代码:</th>
  108. <td>
  109. <a-input
  110. v-model:value="subjectCode"
  111. style="width: 600px"
  112. placeholder="多个科目代码用逗号分隔"
  113. />
  114. </td>
  115. </tr>
  116. </template>
  117. <tr>
  118. <td colspan="2" style="text-align: center">
  119. <a href="##" class="start-btn" @click="start">
  120. <span>开始图片下载</span>
  121. </a>
  122. </td>
  123. </tr>
  124. </table>
  125. </div>
  126. <a-modal
  127. v-model:visible="visible"
  128. title="请选择水印位置"
  129. width="100%"
  130. wrapClassName="full-modal"
  131. ok-text="确定"
  132. cancel-text="取消"
  133. :cancel-button-props="{ style: 'display: none' }"
  134. @ok="visible = false"
  135. >
  136. <div style="position: relative">
  137. <img
  138. id="preview-image"
  139. :src="cardUrl"
  140. draggable="false"
  141. style="width: 100%; height: 100%; user-select: none"
  142. />
  143. <div
  144. ref="dragContainerRef"
  145. style="
  146. color: red;
  147. border: 2px dotted red;
  148. cursor: grab;
  149. width: 80px;
  150. height: 80px;
  151. top: 0;
  152. left: 0;
  153. user-select: none;
  154. "
  155. :style="{ top: yAttr, left: xAttr }"
  156. @mousedown="mouseDownHandler"
  157. class="water-mark"
  158. >
  159. <div>成绩明细</div>
  160. </div>
  161. </div>
  162. </a-modal>
  163. </template>
  164. <script setup lang="ts">
  165. import { store } from "@/store";
  166. import { computed, ref, watch } from "vue";
  167. import { useRouter } from "vue-router";
  168. import { message } from "ant-design-vue";
  169. import { throttle } from "lodash";
  170. const router = useRouter();
  171. let type = ref("");
  172. let template = ref("");
  173. let dir = ref("");
  174. let append = ref(false);
  175. let failover = ref(true);
  176. let watermark = ref(true);
  177. let trackMode = ref("");
  178. let examNumber = ref("");
  179. let subjectCode = ref("");
  180. let x = ref(0.01);
  181. let y = ref(0.03);
  182. const config = store.pageInputs["/image-download"];
  183. if (config) {
  184. type.value = config.type;
  185. template.value = config.template;
  186. dir.value = config.dir;
  187. append.value = config.append;
  188. failover.value = config.failover;
  189. watermark.value = config.watermark;
  190. trackMode.value = config.trackMode;
  191. examNumber.value = config.examNumber || "";
  192. subjectCode.value = config.subjectCode || "";
  193. x.value = config.x || 0.01;
  194. y.value = config.y || 0.03;
  195. }
  196. let ruleExample = computed(() => {
  197. switch (type.value) {
  198. case "1":
  199. return "转存规则范例: " + store.config.imageUrl.sheet;
  200. case "2":
  201. return "转存规则范例: " + store.config.imageUrl.package;
  202. }
  203. return "";
  204. });
  205. const chooseDirectory = (e: MouseEvent) => {
  206. e.preventDefault();
  207. window.electron.dialog
  208. .showOpenDialog({
  209. title: "请选择保存目录",
  210. properties: ["openDirectory"],
  211. })
  212. .then((result) => {
  213. if (result && result.filePaths) {
  214. dir.value = result.filePaths[0];
  215. }
  216. });
  217. };
  218. const start = (e: MouseEvent) => {
  219. e.preventDefault();
  220. if (!type.value) {
  221. message.info("请选择图片类型");
  222. return false;
  223. }
  224. if (!dir.value.trim()) {
  225. message.info("请选择图片转存目录");
  226. return false;
  227. }
  228. if (!template.value.trim()) {
  229. message.info("请填写图片转存规则");
  230. return false;
  231. }
  232. if (type.value === "1") {
  233. if (!x.value) {
  234. message.info("请填写水印起始位置(横向)");
  235. return false;
  236. }
  237. if (!y.value) {
  238. message.info("请填写水印起始位置(纵向)");
  239. return false;
  240. }
  241. if (type.value == "1" && trackMode.value == "") {
  242. message.info("请选择水印模式");
  243. return false;
  244. }
  245. }
  246. store.pageInputs["/image-download"] = {
  247. type: type.value,
  248. template: template.value.trim(),
  249. dir: dir.value.trim(),
  250. append: append.value,
  251. failover: failover.value,
  252. watermark: watermark.value,
  253. trackMode: trackMode.value,
  254. examNumber: examNumber.value.trim(),
  255. subjectCode: subjectCode.value.trim(),
  256. x: x.value - 0,
  257. y: y.value - 0,
  258. };
  259. router.push("/image-download");
  260. };
  261. watch(type, () => {
  262. if (type.value === "2") {
  263. watermark.value = false;
  264. trackMode.value = "";
  265. x.value = 0;
  266. y.value = 0;
  267. examNumber.value = "";
  268. subjectCode.value = "";
  269. }
  270. });
  271. let visible = ref(false);
  272. let cardUrl = computed(() => {
  273. if (trackMode.value === "1") {
  274. return "/img/common_card.jpg";
  275. } else if (trackMode.value === "2") {
  276. return "/img/master-card.jpg";
  277. } else {
  278. return "";
  279. }
  280. });
  281. let dragContainerRef = ref();
  282. const showPreview = () => {
  283. if (cardUrl.value) {
  284. visible.value = true;
  285. } else {
  286. message.info("请选择水印模式");
  287. }
  288. };
  289. let xAttr = computed(() => x.value * 100 + "%");
  290. let yAttr = computed(() => y.value * 100 + "%");
  291. let clientX = 0,
  292. clientY = 0;
  293. const mouseDownHandler = (e: MouseEvent) => {
  294. // console.log(e);
  295. const con = dragContainerRef.value as HTMLDivElement;
  296. con.addEventListener("mousemove", mouseMoveThrottle);
  297. con.addEventListener("mouseup", mouseUpHandler);
  298. con.addEventListener("mouseout", mouseoutHandler);
  299. clientX = e.clientX;
  300. clientY = e.clientY;
  301. };
  302. const mouseMoveHandler = (e: MouseEvent) => {
  303. // console.log(e);
  304. const image = document.querySelector("#preview-image") as HTMLImageElement;
  305. x.value += (e.clientX - clientX) / image.width;
  306. clientX = e.clientX;
  307. y.value += (e.clientY - clientY) / image.height;
  308. clientY = e.clientY;
  309. // console.log(x.value, y.value);
  310. if (x.value < 0) {
  311. x.value = 0;
  312. }
  313. if (x.value > 1) {
  314. x.value = 1;
  315. }
  316. if (y.value < 0) {
  317. y.value = 0;
  318. }
  319. if (y.value > 1) {
  320. y.value = 1;
  321. }
  322. };
  323. const mouseMoveThrottle = throttle((e: MouseEvent) => mouseMoveHandler(e), 100);
  324. const mouseoutHandler = () => {
  325. mouseUpHandler();
  326. };
  327. const mouseUpHandler = () => {
  328. // console.log(e);
  329. const con = dragContainerRef.value as HTMLDivElement;
  330. con.removeEventListener("mousemove", mouseMoveThrottle);
  331. con.removeEventListener("mouseup", mouseUpHandler);
  332. con.removeEventListener("mouseout", mouseoutHandler);
  333. };
  334. </script>
  335. <style>
  336. .full-modal .ant-modal {
  337. max-width: 100%;
  338. top: 0;
  339. padding-bottom: 0;
  340. margin: 0;
  341. }
  342. .full-modal .ant-modal-content {
  343. display: flex;
  344. flex-direction: column;
  345. height: calc(100vh);
  346. }
  347. .full-modal .ant-modal-body {
  348. flex: 1;
  349. }
  350. </style>
  351. <style scoped>
  352. .water-mark {
  353. position: absolute;
  354. }
  355. </style>