TaskCamera-scan-code.uvue 14 KB

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