OutStorageEditSn.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <script setup lang="ts">
  2. import {nextTick, reactive, ref} from 'vue'
  3. import * as XLSX from 'xlsx';
  4. import {Delete} from '@element-plus/icons-vue'
  5. import Drawer from '@/components/Drawer/index.vue'
  6. import type {FormInstance, FormRules} from 'element-plus'
  7. import {ElMessage} from 'element-plus'
  8. import {Storehouse_Device_Check} from '@/api/storehouse'
  9. type Fn = () => void
  10. interface FormSnType {
  11. Id?: number
  12. sn: string
  13. type?: number
  14. T_product_id?: number
  15. T_date?: string
  16. }
  17. const snTable = ref()
  18. const snCount = ref(null)
  19. const SNDataMap = new Map<number, FormSnType[]>()
  20. const tableSnData = ref<FormSnType[]>([])
  21. const ruleSnFormRef = ref<FormInstance>()
  22. const drawerSnRef = ref<InstanceType<typeof Drawer> | null>(null)
  23. const snColumns = [
  24. {type: 'index', label: '序号', width: 80, align: 'center '},
  25. {label: 'SN', prop: 'sn', align: 'center '},
  26. {prop: 'operation', label: '操作', width: 80, fixed: 'right'}
  27. ]
  28. const formSn = reactive<FormSnType>({
  29. Id: undefined,
  30. sn: '',
  31. type: undefined,
  32. T_product_id: undefined,
  33. T_date: undefined
  34. })
  35. const rulesSn = reactive<FormRules>({
  36. sn: [{required: true, message: '请输入SN号', trigger: 'blur'}]
  37. })
  38. const addSn = (e: any) => {
  39. // 阻止默认行为,防止表单提交导致页面跳转
  40. if (e) {
  41. e.preventDefault()
  42. e.stopPropagation()
  43. }
  44. addSns(ruleSnFormRef.value)
  45. return false
  46. }
  47. const addSns = (formEl: FormInstance | undefined) => {
  48. if (!formEl) return
  49. formEl.validate(async valid => {
  50. if (valid) {
  51. if (snCount.value !== null && snCount.value <= tableSnData.value.length) {
  52. ElMessage.error('出库数量与扫码数量不一致')
  53. return
  54. }
  55. if (formSn.sn.length === 16 || formSn.sn.length === 24) {
  56. if (formSn.sn.length == 24) {
  57. formSn.sn = formSn.sn.substring(2, formSn.sn.length - 6)
  58. }
  59. const res: any = await Storehouse_Device_Check({
  60. T_product_id: formSn.T_product_id,
  61. T_sn: formSn.sn,
  62. T_type: formSn.type,
  63. T_date: formSn.T_date
  64. })
  65. if (res.Code === 200) {
  66. tableSnData.value.unshift({sn: formSn.sn})
  67. tableSnData.value = tableSnData.value.filter((value, index, self) => { //去重
  68. return self.findIndex(t => (t.sn === value.sn)) === index;
  69. });
  70. nextTick(() => {
  71. resetSnForm(ruleSnFormRef.value)
  72. })
  73. }
  74. } else {
  75. ElMessage.error('扫描设备异常')
  76. nextTick(() => {
  77. resetSnForm(ruleSnFormRef.value)
  78. })
  79. return
  80. }
  81. }
  82. })
  83. }
  84. const deleteSn = (row: any) => {
  85. const index = tableSnData.value.findIndex((item: any) => row.sn === item.sn)
  86. tableSnData.value.splice(index, 1)
  87. }
  88. const callbackSnDrawer = (done: Fn) => {
  89. SNDataMap.set(formSn.Id as number, tableSnData.value)
  90. let arrs = tableSnData.value.map((item: any) => item.sn);
  91. emit('onCount', SNDataMap.get(formSn.Id as number)?.length, formSn.Id as number, arrs)
  92. done()
  93. nextTick(() => {
  94. resetSnForm(ruleSnFormRef.value)
  95. tableSnData.value = []
  96. })
  97. }
  98. const emit = defineEmits<{ (event: 'onCount', value: any, id: number, mapArr: any): void }>()
  99. const resetSnForm = (formEl: FormInstance | undefined) => {
  100. if (!formEl) return
  101. formEl.resetFields()
  102. }
  103. const addDeviceSn = (id: number, type: number, T_product_id: number, date?: string) => {
  104. if (!date) {
  105. ElMessage.warning('请先填写出库日期')
  106. return
  107. }
  108. formSn.Id = id
  109. formSn.type = type
  110. formSn.T_product_id = T_product_id
  111. formSn.T_date = date
  112. if (SNDataMap.has(id)) {
  113. tableSnData.value = SNDataMap.get(id) as FormSnType[]
  114. } else {
  115. SNDataMap.set(id, [])
  116. }
  117. drawerSnRef.value?.openDrawer()
  118. }
  119. const drawer = ref(false)
  120. /**
  121. * 导入xlsx
  122. */
  123. // 组件状态变化的回调
  124. const uploadExcelFile = (event: any) => {
  125. const files = event.target.files;
  126. if (files.length === 0) return;
  127. const file = files[0];
  128. const reader = new FileReader();
  129. reader.onload = (e: any) => {
  130. const data = new Uint8Array(e.target.result);
  131. const workbook = XLSX.read(data, {type: 'array'});
  132. // 假设我们知道第一个工作表包含我们需要的数据
  133. const firstSheetName = workbook.SheetNames[0];
  134. const worksheet: any = workbook.Sheets[firstSheetName];
  135. // 假设我们知道 t_sn 是第一列
  136. const tSnColumn = 'A'; // 或者使用 XLSX.utils.decode_col(columnNumber) 来从列号获取列名
  137. const columnNumber = XLSX.utils.decode_col(tSnColumn); // 但实际上我们可能直接知道列号,如 0
  138. // 使用 sheet_to_json 但只选择我们需要的列
  139. const json = XLSX.utils.sheet_to_json(worksheet, {
  140. header: 1,
  141. range: XLSX.utils.decode_range(worksheet['!ref'])
  142. });
  143. // 如果表头不是第一行,或者你不想要表头,可以调整 header 选项
  144. // 提取 t_sn 列的数据(假设表头在第一行,且 t_sn 是第一列)
  145. let tSnData: any = json.map((row: any) => row[0]); // 假设 t_sn 是第一列,所以使用 row[0]
  146. let snArray = tSnData.map((sn: any) => ({sn: sn}));
  147. snArray = snArray.slice(1)
  148. let arrs = [...tableSnData.value, ...snArray]
  149. // 去重
  150. let seen = new Set();
  151. tableSnData.value = arrs.filter((item: any) => {
  152. return seen.has(item.sn) ? false : seen.add(item.sn) || true;
  153. });
  154. console.log('打印', tableSnData.value)
  155. // 注意:如果 t_sn 列不是第一列,你需要调整索引号
  156. // 例如,如果 t_sn 是第三列,则使用 row[2]
  157. };
  158. reader.readAsArrayBuffer(file);
  159. };
  160. const getDeviceSn = () => SNDataMap
  161. const clearDeviceSn = () => SNDataMap.clear()
  162. const deleteDeviceSn = (id: number) => SNDataMap.delete(id)
  163. defineExpose({
  164. getDeviceSn,
  165. addDeviceSn,
  166. clearDeviceSn,
  167. deleteDeviceSn, tableSnData, drawerSnRef, snCount
  168. })
  169. </script>
  170. <template>
  171. <Drawer ref="drawerSnRef" :handleClose="callbackSnDrawer" :snCount="snCount" :closeModal="false" size="50%">
  172. <el-card class="box-card" shadow="never">
  173. <template #header>
  174. <div class="sn-header">
  175. <el-form ref="ruleSnFormRef" :model="formSn" :rules="rulesSn" @submit.prevent="addSns(ruleSnFormRef)">
  176. <el-form-item label="SN:" label-width="120px" prop="sn">
  177. <el-input
  178. v-model="formSn.sn"
  179. type="text"
  180. placeholder="请输入SN"
  181. @keyup.enter.prevent="addSn"
  182. @keydown.enter.prevent
  183. class="w-50"
  184. />
  185. </el-form-item>
  186. </el-form>
  187. <el-button type="primary" @click="addSns(ruleSnFormRef)">添加</el-button>
  188. <el-button type="success" @click="drawer = true">导入xlsx</el-button>
  189. </div>
  190. </template>
  191. <div>数量:{{ tableSnData.length }}</div>
  192. <el-table
  193. ref="snTable"
  194. :data="tableSnData"
  195. style="width: 100%; height: 99%"
  196. :header-cell-style="{
  197. background: '#dedfe0',
  198. height: '50px'
  199. }"
  200. >
  201. <template v-for="item in snColumns" :key="item">
  202. <el-table-column v-if="item.type === 'index'" v-bind="item"/>
  203. <el-table-column show-overflow-tooltip v-if="item.prop" align="center" v-bind="item">
  204. <template #default="{ row }">
  205. <el-button
  206. v-if="item.prop === 'operation'"
  207. link
  208. type="danger"
  209. size="small"
  210. :icon="Delete"
  211. @click="deleteSn(row)"
  212. >删除
  213. </el-button
  214. >
  215. </template>
  216. </el-table-column>
  217. </template>
  218. </el-table>
  219. </el-card>
  220. <el-drawer v-model="drawer" title="导入xlsx" size="50%" :destroy-on-close="true">
  221. <input type="file" @change="uploadExcelFile" accept=".xlsx, .xls"/>
  222. </el-drawer>
  223. </Drawer>
  224. </template>
  225. <style scoped lang="scss">
  226. .box-card {
  227. height: 100%;
  228. :deep(.el-card__body) {
  229. height: calc(100% - 70px);
  230. }
  231. .sn-header {
  232. display: flex;
  233. justify-content: end;
  234. }
  235. }
  236. </style>