camera-scan-code.uvue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. <template>
  2. <!-- #ifdef APP -->
  3. <scroll-view style="flex:1">
  4. <!-- #endif -->
  5. <!--
  6. <camera style="width: 100%; height: 300px;" :resolution="'high'" :mode="'scanCode'" @scancode="handleScanCode">
  7. </camera> -->
  8. <view class="my-page">
  9. <swiper class="swiper-box my-swiper" :current="swiperDotIndex" :indicator-dots="true">
  10. <swiper-item v-for="item in data">
  11. <view class="swiper-item" @tap="previewSingleImage(item.image)">
  12. <image class="my-image" :src="item.image" mode="aspectFill" />
  13. </view>
  14. <view>
  15. <text class="demo-view-label">{{ item?.name ?? ''}}</text>
  16. </view>
  17. </swiper-item>
  18. </swiper>
  19. <view class="camera-scan-code-table">
  20. <view class="camera-scan-code-table-pair" v-if="joinRecord.imgname!=''">
  21. <view class="camera-scan-code-table-pair-label">
  22. <text>已存图片(最多能存{{joinRecord.num}}张)</text>
  23. </view>
  24. <view class="camera-scan-code-table-pair-value" v-for="itemName in joinRecord.imgname.split(',')">
  25. <text class="txt">{{ itemName }}</text>
  26. </view>
  27. </view>
  28. <view class="camera-scan-code-table-pair">
  29. <view class="camera-scan-code-table-pair-label">
  30. <text>待存图片</text>
  31. </view>
  32. <view class="camera-scan-code-table-pair-value">
  33. <text class="txt">{{ dyImgName+"-"+minAvailableNumber+".jpg"}}</text>
  34. </view>
  35. </view>
  36. </view>
  37. <view class="camera-scan-code-back-wrap">
  38. <button class="btn block bg-blue lg round" @click="saveImage" style="min-width: 250px;">
  39. 保存图片
  40. </button>
  41. </view>
  42. <view class="camera-scan-code-back-wrap">
  43. <button type="default" class="btn bg-blue round" @click="navigateBack">返回拍照</button>
  44. <button type="default" class="btn bg-blue round" @click="navigateExit">退出拍照</button>
  45. </view>
  46. </view>
  47. <canvas id="canvas"
  48. style="position: absolute; left: -9999px; top: -9999px; width: 300px; height: 300px;"></canvas>
  49. <!-- #ifdef APP -->
  50. </scroll-view>
  51. <!-- #endif -->
  52. </template>
  53. <script lang="uts">
  54. type CameraScanCodeResult = {
  55. type : string | null;
  56. result : string | null;
  57. }
  58. type ImageItem = {
  59. image : string,
  60. name : string,
  61. checked : boolean
  62. }
  63. import { getJoinList, JoinRecord, updateData } from '@/api/work';
  64. import { BitmapUtils } from '@/nativeplugins/opencv/android/uts/BitmapUtils.uts'
  65. export default {
  66. data() {
  67. return {
  68. result: null as CameraScanCodeResult | null,
  69. swiperDotIndex: 0 as number,
  70. path: '',
  71. recordId: 0,
  72. imgArrLen: 0,
  73. joinRecord: {
  74. sxid: 0,
  75. senum: 0,
  76. photoitem: "",
  77. productno: "",
  78. descb: "",
  79. part: "",
  80. date: "",
  81. partno: "",
  82. num: 0,
  83. status: 1,
  84. urlspl: "",
  85. imgname: "",
  86. urlpdt: "",
  87. pid: 0,
  88. workorder: "",
  89. invname: ""
  90. } as JoinRecord,
  91. senum: 0,
  92. maxcount: 0,
  93. dyImgName: '',
  94. minAvailableNumber: 1,
  95. data: [{
  96. image: '/static/images/banner/banner01.png',
  97. name: 'banner01.png',
  98. checked: true
  99. }] as ImageItem[],
  100. canvas: null as UniCanvasElement | null,
  101. canvasContext: null as CanvasContext | null,
  102. renderingContext: null as CanvasRenderingContext2D | null,
  103. }
  104. },
  105. onBackPress() {
  106. // 覆盖系统默认的返回行为,返回到指定页面
  107. //?part=${part}&num=${num}&pid=${pid}
  108. uni.navigateTo({
  109. url: `/pages/work/record/RecordList?part=${this.joinRecord.part}&num=${this.maxcount}&pid=${this.joinRecord.pid}&senum=${this.joinRecord.senum}`,
  110. // 修改动画方向为从左到右退回
  111. animationType: 'slide-in-left', // 使用从左到右滑出的动画效果
  112. animationDuration: 300 // 动画持续时间,单位ms
  113. })
  114. // 返回true表示拦截默认返回行为
  115. return true
  116. },
  117. onLoad(options) {
  118. this.path = options?.['path'] ?? ''
  119. let recordId = options?.['recordId'] ?? ''
  120. let senum = options?.['senum'] ?? ''
  121. let maxcount = options?.['maxcount'] ?? ''
  122. console.log(this.path)
  123. if (this.path != '') {
  124. this.data = []
  125. this.data.push({ image: this.path, name: '', checked: true })
  126. }
  127. if (recordId != '') {
  128. this.recordId = parseInt(recordId)
  129. getJoinList('app_media_record as r', 'app_media_info as i', 'r.*,i.workorder,i.invname', 'r.pid=i.pdid', 'sxid', recordId, null).then((res : UTSJSONObject) => {
  130. let dataList = res?.['data'] as UTSJSONObject[] ?? Array<UTSJSONObject>()
  131. if (dataList != null && dataList.length > 0) {
  132. dataList.forEach(item => {
  133. if (item != null) {
  134. let record = JSON.parse<JoinRecord>(item.toJSONString());
  135. if (record != null) {
  136. // this.senum = record.num;
  137. this.dyImgName = record.workorder + record.invname + record.part + record.photoitem + record.partno;
  138. this.joinRecord = record
  139. this.imgArrLen = record.imgname.indexOf(",") > -1 ? record.imgname.split(",").length : (record.imgname != '' ? 1 : 0)
  140. // 计算最小可用编号
  141. this.calculateMinAvailableNumber();
  142. }
  143. }
  144. });
  145. }
  146. });
  147. }
  148. if (senum != '') {
  149. this.senum = parseInt(senum)
  150. }
  151. if (maxcount != '') {
  152. this.maxcount = parseInt(maxcount)
  153. }
  154. // 异步调用方式, 跨平台写法
  155. uni.createCanvasContextAsync({
  156. id: 'canvas',
  157. component: this,
  158. success: (context : CanvasContext) => {
  159. this.canvasContext = context;
  160. this.renderingContext = context.getContext('2d')!;
  161. this.canvas = this.renderingContext!.canvas;
  162. }
  163. })
  164. },
  165. onReady() {
  166. // 确认canvas元素可正常使用
  167. // UTS中不支持uni.createCanvasContext,改用其他方式验证
  168. console.log('Canvas元素已准备就绪');
  169. },
  170. methods: {
  171. navigateBack() {
  172. //uni.navigateBack()
  173. uni.navigateTo({
  174. url: `/pages/work/record/Camera?id=${this.recordId}&senum=${this.senum}`
  175. })
  176. },
  177. navigateExit() {
  178. uni.navigateTo({
  179. url: `/pages/work/record/InfoDetail?id=${this.joinRecord.pid}`
  180. })
  181. },
  182. checkboxChange() {
  183. console.log("选择图片名称")
  184. },
  185. chooseImage() {
  186. uni.chooseImage({
  187. sourceType: ['album'],
  188. success: (res) => {
  189. //this.data = res.tempFilePaths; // 获取选中的图片路径
  190. this.data = []
  191. for (let i = 0; i < res.tempFilePaths.length; i++) {
  192. this.data.push({ image: res.tempFilePaths[i], name: '', checked: true })
  193. }
  194. },
  195. fail: (err) => {
  196. console.error('选择图片失败', err);
  197. }
  198. });
  199. },
  200. async saveImage() {
  201. //保存图片进入相册文件
  202. const saveImageToDatabase = () => {
  203. let updateImgs = ''
  204. let updateNames = ''
  205. let updateStatus = ''
  206. // 动态生成最小的可用图片编号
  207. const getMinAvailableNumber = () => {
  208. if (this.imgArrLen === 0) return 1;
  209. // 从已有的图片名称中提取编号
  210. const existingNumbers = new Set<number>();
  211. const imgNames = this.joinRecord.imgname.split(',');
  212. imgNames.forEach((name : string) => {
  213. if (name != '') {
  214. // 提取图片名称中的数字部分
  215. const match = name.match(/-(\d+)\.jpg$/);
  216. if (match != null && match[1] != null) {
  217. const numStr = match[1] as string;
  218. existingNumbers.add(parseInt(numStr));
  219. }
  220. }
  221. });
  222. // 查找1到所需图片数量之间最小的未使用编号
  223. for (let i = 1; i <= this.joinRecord.num; i++) {
  224. if (!existingNumbers.has(i)) {
  225. return i;
  226. }
  227. }
  228. // 如果1到所需数量都被使用了,则使用当前数量+1
  229. return this.imgArrLen + 1;
  230. };
  231. const minAvailableNumber = getMinAvailableNumber();
  232. const newImgName = this.dyImgName + "-" + minAvailableNumber + ".jpg";
  233. const relativePath = `/uploadImgs/${newImgName}`
  234. if (this.imgArrLen == 0) {
  235. updateImgs = relativePath
  236. updateNames = newImgName;
  237. } else {
  238. updateImgs = this.joinRecord.urlpdt + "," + relativePath
  239. updateNames = this.joinRecord.imgname + "," + newImgName;
  240. }
  241. let updatedData = "imgname='" + updateNames + "',urlpdt='" + updateImgs + "'"
  242. if (this.joinRecord.num === this.imgArrLen + 1) {
  243. updateStatus = "status='3'"
  244. updatedData = updatedData + "," + updateStatus
  245. } else if (this.joinRecord.status === 1) {
  246. updateStatus = "status='2'"
  247. updatedData = updatedData + "," + updateStatus
  248. }
  249. console.log(updatedData)
  250. updateData('app_media_record', updatedData, 'sxid', this.recordId.toString()).then((res : UTSJSONObject) => {
  251. let data = res?.['data'] as boolean ?? false
  252. if (data != null && data == true) {
  253. uni.showToast({
  254. title: "保存成功!",
  255. });
  256. const newPath = `${uni.env.USER_DATA_PATH}/uploadImgs/${newImgName}`
  257. this.renameFile(this.path, newPath)
  258. uni.navigateTo({
  259. url: `/pages/work/record/RecordList?part=${this.joinRecord.part}&senum=${this.senum}&pid=${this.joinRecord.pid}`,
  260. // 修改动画方向为从左到右退回
  261. animationType: 'slide-in-left', // 使用从左到右滑出的动画效果
  262. animationDuration: 300 // 动画持续时间,单位ms
  263. })
  264. }
  265. });
  266. }
  267. if (this.path == '') {
  268. uni.showToast({
  269. title: '没有拍照文件',
  270. icon: 'error',
  271. duration: 2000
  272. });
  273. return;
  274. }
  275. if (this.imgArrLen == this.joinRecord.num) {
  276. uni.showToast({
  277. title: '拍照图片已满',
  278. icon: 'error',
  279. duration: 2000
  280. });
  281. return;
  282. }
  283. // 检测图片清晰度
  284. try {
  285. uni.showLoading({ title: '检测图片清晰度...' });
  286. this.renderingContext!.clearRect(0, 0, 1000, 1000)
  287. let image = this.canvasContext!.createImage();
  288. image.src = this.path
  289. let score = 0
  290. let level = ""
  291. image.onload = () => {
  292. this.renderingContext?.drawImage(image, 0, 0, image.width, image.height);
  293. console.log(image.width)
  294. console.log(image.height)
  295. setTimeout(() => {
  296. try {
  297. // 将canvas内容转换为base64格式
  298. const base64Data = this.canvasContext!.toDataURL();
  299. const result = BitmapUtils.checkImageClarityFromBase64(base64Data)
  300. console.log(result)
  301. console.log("清晰度检测结果得分:", result?.['score']);
  302. score = result?.['score'] as number
  303. level = result?.['level'] as string
  304. uni.hideLoading();
  305. const customThreshold = 3500;
  306. const isClearWithCustomThreshold = score > customThreshold;
  307. if (!isClearWithCustomThreshold) {
  308. // 不清晰的图片,提示用户
  309. uni.showModal({
  310. title: '提示',
  311. content: `图片清晰度检测结果:${level}\n图片可能不够清晰,是否继续保存?`,
  312. success: (res) => {
  313. if (!res.confirm) {
  314. // 用户取消保存
  315. return;
  316. }
  317. // 用户确认后执行保存流程
  318. saveImageToDatabase();
  319. },
  320. fail: () => {
  321. // 模态框失败时继续保存
  322. saveImageToDatabase();
  323. }
  324. });
  325. } else {
  326. // 图片清晰,直接保存
  327. saveImageToDatabase();
  328. }
  329. } catch (error) {
  330. console.error("转换图片为Base64失败:", error);
  331. }
  332. }, 100);
  333. }
  334. } catch (error) {
  335. uni.hideLoading();
  336. console.error('图片清晰度检测失败:', error);
  337. // 检测失败时继续保存流程
  338. uni.showToast({
  339. title: '清晰度检测失败,继续保存',
  340. icon: 'none',
  341. duration: 2000
  342. });
  343. saveImageToDatabase();
  344. }
  345. return;
  346. },
  347. previewSingleImage(imageUrl : string) {
  348. uni.previewImage({
  349. urls: [imageUrl], // 需要预览的图片链接列表
  350. current: 0, // 当前显示图片的索引
  351. indicator: 'number', // 图片指示器样式
  352. loop: false // 是否可循环预览
  353. });
  354. },
  355. renameFile(oldPath : string, newPath : string) {
  356. // 先拷贝文件到新路径
  357. uni.getFileSystemManager().copyFile({
  358. srcPath: oldPath,
  359. destPath: newPath,
  360. success: function () {
  361. // 可选:删除原文件,如果你需要释放空间的话
  362. uni.getFileSystemManager().unlink({
  363. filePath: oldPath,
  364. success: function () {
  365. console.log('原文件已删除');
  366. },
  367. fail: function (unlinkErr) {
  368. console.error('删除原文件失败', unlinkErr);
  369. }
  370. });
  371. console.log('文件重命名成功');
  372. },
  373. fail: function (copyErr) {
  374. console.error('拷贝文件失败', copyErr);
  375. }
  376. });
  377. },
  378. handleScanCode(ev : UniCameraScanCodeEvent) {
  379. const deatil = ev.detail;
  380. this.result = {
  381. type: deatil.type,
  382. result: deatil.result
  383. } as CameraScanCodeResult
  384. },
  385. // 计算最小的可用图片编号
  386. calculateMinAvailableNumber() {
  387. if (this.imgArrLen === 0) {
  388. this.minAvailableNumber = 1;
  389. return;
  390. }
  391. // 从已有的图片名称中提取编号
  392. const existingNumbers = new Set<number>();
  393. const imgNames = this.joinRecord.imgname.split(',');
  394. imgNames.forEach((name : string) => {
  395. if (name != '') {
  396. // 提取图片名称中的数字部分
  397. const match = name.match(/-(\d+)\.jpg$/);
  398. if (match != null && match[1] != null) {
  399. const numStr = match[1] as string;
  400. existingNumbers.add(parseInt(numStr));
  401. }
  402. }
  403. });
  404. // 查找1到所需图片数量之间最小的未使用编号
  405. for (let i = 1; i <= this.joinRecord.num; i++) {
  406. if (!existingNumbers.has(i)) {
  407. this.minAvailableNumber = i;
  408. return;
  409. }
  410. }
  411. // 如果1到所需数量都被使用了,则使用当前数量+1
  412. this.minAvailableNumber = this.imgArrLen + 1;
  413. },
  414. }
  415. }
  416. </script>
  417. <style>
  418. .camera-scan-code-back-wrap {
  419. display: flex;
  420. justify-content: center;
  421. align-items: center;
  422. flex-direction: row;
  423. }
  424. .camera-scan-code-table {
  425. background-color: white;
  426. margin-top: 20px;
  427. }
  428. .camera-scan-code-table-pair {
  429. height: 100px;
  430. flex-direction: column;
  431. justify-content: space-between;
  432. align-items: center;
  433. }
  434. .camera-scan-code-table-pair-label {
  435. flex-grow: 1;
  436. margin-left: 15px;
  437. }
  438. .camera-scan-code-table-pair-value {
  439. flex-grow: 2;
  440. flex-direction: column;
  441. }
  442. .camera-scan-code-table-pair-value .txt {
  443. font-size: 14px;
  444. }
  445. .camera-scan-code-table-top-line {
  446. border-top: 1px solid #eee;
  447. }
  448. .my-page {
  449. display: flex;
  450. flex-direction: column;
  451. box-sizing: border-box;
  452. background-color: #fff;
  453. /* #ifndef APP-ANDROID */
  454. min-height: 100%;
  455. /* #endif */
  456. height: 100%;
  457. }
  458. .swiper {
  459. height: 400px;
  460. }
  461. .swiper-box {
  462. height: 300px;
  463. }
  464. .swiper-item {
  465. /* #ifndef APP-NVUE */
  466. display: flex;
  467. /* #endif */
  468. flex-direction: column;
  469. justify-content: center;
  470. align-items: center;
  471. color: #fff;
  472. height: 400px;
  473. line-height: 400px;
  474. }
  475. .btn {
  476. /*margin-top: 30px; */
  477. height: 45px;
  478. margin: 20px 20px;
  479. }
  480. </style>