ScanView.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. <template>
  2. <WorkerLayout>
  3. <div class="scan-view">
  4. <a-card title="扫码计件">
  5. <!-- 扫码区域 -->
  6. <div class="scan-area">
  7. <a-button
  8. type="primary"
  9. size="large"
  10. block
  11. @click="startScan"
  12. :loading="scanning"
  13. v-if="!scanning"
  14. >
  15. <template #icon><ScanOutlined /></template>
  16. 开始扫描
  17. </a-button>
  18. <div v-if="scanning" class="scanning">
  19. <a-spin tip="正在扫描..." />
  20. <a-button @click="stopScan" class="mt-3">停止扫描</a-button>
  21. </div>
  22. </div>
  23. <!-- 手动输入二维码 -->
  24. <a-divider>或手动输入</a-divider>
  25. <a-input
  26. v-model:value="qrCode"
  27. placeholder="请输入二维码内容"
  28. size="large"
  29. @pressEnter="handleQrCode"
  30. />
  31. </a-card>
  32. <!-- 计件表单 -->
  33. <a-card title="计件信息" class="mt-3" v-if="bundleInfo">
  34. <a-descriptions :column="1" size="small">
  35. <a-descriptions-item label="扎号">{{ bundleInfo.bundle_no }}</a-descriptions-item>
  36. <a-descriptions-item label="尺码">{{ bundleInfo.size }}</a-descriptions-item>
  37. <a-descriptions-item label="数量">{{ bundleInfo.quantity }}</a-descriptions-item>
  38. <a-descriptions-item label="工单">{{ bundleInfo.order_no }}</a-descriptions-item>
  39. <a-descriptions-item label="款号">{{ bundleInfo.style_name }}</a-descriptions-item>
  40. </a-descriptions>
  41. <a-form layout="vertical" class="mt-3">
  42. <a-form-item label="工序" required>
  43. <a-select v-model:value="processId" placeholder="请选择工序" size="large">
  44. <a-select-option v-for="p in processes" :key="p.id" :value="p.id">
  45. {{ p.name }}
  46. </a-select-option>
  47. </a-select>
  48. </a-form-item>
  49. <a-form-item label="数量" required>
  50. <a-input-number
  51. v-model:value="quantity"
  52. :min="1"
  53. :max="bundleInfo.quantity"
  54. size="large"
  55. style="width: 100%"
  56. />
  57. </a-form-item>
  58. <a-button
  59. type="primary"
  60. size="large"
  61. block
  62. @click="handleSubmit"
  63. :loading="submitting"
  64. >
  65. 确认计件
  66. </a-button>
  67. </a-form>
  68. </a-card>
  69. </div>
  70. </WorkerLayout>
  71. </template>
  72. <script setup lang="ts">
  73. import { ref, onMounted } from 'vue';
  74. import { message } from 'ant-design-vue';
  75. import { ScanOutlined } from '@ant-design/icons-vue';
  76. import WorkerLayout from '@/components/WorkerLayout.vue';
  77. import { RecordApi, ProcessApi, createApiClient } from '@smartcut/api-client';
  78. import { STORAGE_KEYS } from '@smartcut/shared-utils';
  79. import { getApiBaseUrl } from '@/apiConfig';
  80. import type { Process } from '@smartcut/types';
  81. const scanning = ref(false);
  82. const qrCode = ref('');
  83. const bundleInfo = ref<any>(null);
  84. const processes = ref<Process[]>([]);
  85. const processId = ref<number | null>(null);
  86. const quantity = ref(1);
  87. const submitting = ref(false);
  88. const apiClient = createApiClient({
  89. baseURL: getApiBaseUrl(),
  90. tokenKey: STORAGE_KEYS.WORKER_TOKEN
  91. });
  92. const recordApi = new RecordApi(apiClient);
  93. const processApi = new ProcessApi(apiClient);
  94. onMounted(async () => {
  95. try {
  96. processes.value = await processApi.getProcesses();
  97. } catch (error) {
  98. message.error('加载工序列表失败');
  99. }
  100. });
  101. async function startScan() {
  102. scanning.value = true;
  103. // 实际项目中这里调用摄像头扫码API
  104. // 由于浏览器限制,需要HTTPS环境
  105. message.info('扫码功能需要在HTTPS环境下使用,请手动输入二维码');
  106. scanning.value = false;
  107. }
  108. function stopScan() {
  109. scanning.value = false;
  110. }
  111. async function handleQrCode() {
  112. if (!qrCode.value) {
  113. message.error('请输入二维码内容');
  114. return;
  115. }
  116. try {
  117. // 解析二维码获取扎号信息
  118. const response = await apiClient.get('/worker/bundle-info', {
  119. params: { qr_code: qrCode.value }
  120. });
  121. bundleInfo.value = response.data.data;
  122. quantity.value = bundleInfo.value.quantity;
  123. message.success('已识别扎号');
  124. } catch (error: any) {
  125. message.error(error.response?.data?.msg || '二维码无效');
  126. }
  127. }
  128. async function handleSubmit() {
  129. if (!processId.value) {
  130. message.error('请选择工序');
  131. return;
  132. }
  133. if (!bundleInfo.value) {
  134. message.error('请先扫描或输入二维码');
  135. return;
  136. }
  137. submitting.value = true;
  138. try {
  139. await recordApi.scanPiece({
  140. qr_code: qrCode.value,
  141. process_id: processId.value,
  142. quantity: quantity.value
  143. });
  144. message.success('计件成功');
  145. // 重置表单
  146. qrCode.value = '';
  147. bundleInfo.value = null;
  148. processId.value = null;
  149. quantity.value = 1;
  150. } catch (error: any) {
  151. message.error(error.response?.data?.msg || '计件失败');
  152. } finally {
  153. submitting.value = false;
  154. }
  155. }
  156. </script>
  157. <style scoped lang="postcss">
  158. .scan-view { padding: 0; }
  159. .scan-area {
  160. text-align: center;
  161. padding: 20px 0;
  162. }
  163. .scanning {
  164. text-align: center;
  165. padding: 40px 0;
  166. }
  167. .mt-3 {
  168. margin-top: 12px;
  169. }
  170. </style>