hook.tsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. import dayjs from "dayjs";
  2. import { message } from "@/utils/message";
  3. import { addDialog } from "@/components/ReDialog";
  4. import type { PaginationProps } from "@pureadmin/table";
  5. import { deviceDetection } from "@pureadmin/utils";
  6. import {
  7. getInventoryList,
  8. increaseStock,
  9. adjustStock
  10. } from "@/api/inventory";
  11. import { getProductList } from "@/api/product";
  12. import { getDeviceList } from "@/api/device";
  13. import { type Ref, ref, toRaw, reactive, onMounted } from "vue";
  14. import {
  15. ElForm,
  16. ElFormItem,
  17. ElInput,
  18. ElInputNumber,
  19. ElSelect,
  20. ElOption
  21. } from "element-plus";
  22. import type { InventoryItem, InventorySearchForm, StockInForm, StockAdjustForm } from "./types";
  23. export function useInventory(tableRef: Ref) {
  24. const form = reactive<InventorySearchForm>({
  25. deviceId: "",
  26. productId: "",
  27. lowStock: false
  28. });
  29. const formRef = ref();
  30. const ruleFormRef = ref();
  31. const dataList = ref([]);
  32. const loading = ref(true);
  33. const deviceOptions = ref([]);
  34. const productOptions = ref([]);
  35. const pagination = reactive<PaginationProps>({
  36. total: 0,
  37. pageSize: 10,
  38. currentPage: 1,
  39. background: true
  40. });
  41. const columns: TableColumnList = [
  42. {
  43. label: "设备ID",
  44. prop: "deviceId",
  45. minWidth: 120
  46. },
  47. {
  48. label: "设备名称",
  49. prop: "deviceName",
  50. minWidth: 120
  51. },
  52. {
  53. label: "商品编码",
  54. prop: "productCode",
  55. minWidth: 120
  56. },
  57. {
  58. label: "商品名称",
  59. prop: "productName",
  60. minWidth: 150
  61. },
  62. {
  63. label: "库存数量",
  64. prop: "stock",
  65. minWidth: 100,
  66. cellRenderer: ({ row }) => (
  67. <el-tag type={row.stock <= 5 ? "danger" : row.stock <= 10 ? "warning" : "success"}>
  68. {row.stock}
  69. </el-tag>
  70. )
  71. },
  72. {
  73. label: "货架号",
  74. prop: "shelfNum",
  75. minWidth: 80
  76. },
  77. {
  78. label: "位置",
  79. prop: "position",
  80. minWidth: 80
  81. },
  82. {
  83. label: "更新时间",
  84. prop: "lastUpdateTime",
  85. minWidth: 160,
  86. formatter: ({ lastUpdateTime }) =>
  87. lastUpdateTime ? dayjs(lastUpdateTime).format("YYYY-MM-DD HH:mm:ss") : "-"
  88. },
  89. {
  90. label: "操作",
  91. fixed: "right",
  92. width: 150,
  93. slot: "operation"
  94. }
  95. ];
  96. // 搜索
  97. async function onSearch() {
  98. loading.value = true;
  99. try {
  100. const searchParams: any = {
  101. page: pagination.currentPage,
  102. pageSize: pagination.pageSize
  103. };
  104. // 只添加非空的搜索条件
  105. if (form.deviceId) searchParams.deviceId = form.deviceId;
  106. if (form.productId) searchParams.productId = form.productId;
  107. if (form.lowStock) searchParams.lowStock = form.lowStock;
  108. const { data } = await getInventoryList(searchParams);
  109. if (data) {
  110. dataList.value = data.list || [];
  111. pagination.total = Number(data.total) || 0;
  112. }
  113. } catch (error) {
  114. console.error("获取库存列表失败:", error);
  115. } finally {
  116. setTimeout(() => {
  117. loading.value = false;
  118. }, 300);
  119. }
  120. }
  121. // 重置表单
  122. const resetForm = formEl => {
  123. if (!formEl) return;
  124. formEl.resetFields();
  125. pagination.currentPage = 1;
  126. onSearch();
  127. };
  128. // 分页
  129. function handleSizeChange(val: number) {
  130. pagination.pageSize = val;
  131. onSearch();
  132. }
  133. function handleCurrentChange(val: number) {
  134. pagination.currentPage = val;
  135. onSearch();
  136. }
  137. // 上货
  138. const stockInForm = reactive<StockInForm>({
  139. deviceId: "",
  140. productId: 0,
  141. productCode: "",
  142. productName: "",
  143. quantity: 1,
  144. shelfNum: 1,
  145. position: ""
  146. });
  147. function handleStockIn() {
  148. addDialog({
  149. title: "上货",
  150. width: "40%",
  151. draggable: true,
  152. fullscreen: deviceDetection(),
  153. closeOnClickModal: false,
  154. contentRenderer: () => (
  155. <ElForm ref={ruleFormRef} model={stockInForm} label-width="100px">
  156. <ElFormItem label="设备" prop="deviceId" rules={[{ required: true, message: "请选择设备", trigger: "change" }]}>
  157. <ElSelect v-model={stockInForm.deviceId} placeholder="请选择设备" class="w-full!">
  158. {deviceOptions.value.map(item => (
  159. <ElOption key={item.deviceId} label={item.deviceName} value={item.deviceId} />
  160. ))}
  161. </ElSelect>
  162. </ElFormItem>
  163. <ElFormItem label="商品" prop="productId" rules={[{ required: true, message: "请选择商品", trigger: "change" }]}>
  164. <ElSelect v-model={stockInForm.productId} placeholder="请选择商品" class="w-full!">
  165. {productOptions.value.map(item => (
  166. <ElOption key={item.id} label={item.name} value={item.id} />
  167. ))}
  168. </ElSelect>
  169. </ElFormItem>
  170. <ElFormItem label="数量" prop="quantity" rules={[{ required: true, message: "请输入数量", trigger: "blur" }]}>
  171. <ElInputNumber v-model={stockInForm.quantity} min={1} class="w-full!" />
  172. </ElFormItem>
  173. <ElFormItem label="货架号" prop="shelfNum">
  174. <ElInputNumber v-model={stockInForm.shelfNum} min={1} class="w-full!" />
  175. </ElFormItem>
  176. <ElFormItem label="位置" prop="position">
  177. <ElInput v-model={stockInForm.position} placeholder="请输入位置" clearable />
  178. </ElFormItem>
  179. </ElForm>
  180. ),
  181. beforeSure: async (done) => {
  182. const valid = await ruleFormRef.value.validate().catch(() => false);
  183. if (!valid) return;
  184. try {
  185. const res = await increaseStock(toRaw(stockInForm));
  186. if (res.code === 200) {
  187. message("上货成功", { type: "success" });
  188. done();
  189. onSearch();
  190. } else {
  191. message(res.message || "上货失败", { type: "error" });
  192. }
  193. } catch (error) {
  194. message("上货失败", { type: "error" });
  195. }
  196. }
  197. });
  198. }
  199. // 库存调整
  200. const adjustForm = reactive<StockAdjustForm>({
  201. deviceId: "",
  202. productId: 0,
  203. newStock: 0,
  204. remark: ""
  205. });
  206. function handleAdjust(row: InventoryItem) {
  207. adjustForm.deviceId = row.deviceId;
  208. adjustForm.productId = row.productId;
  209. adjustForm.newStock = row.stock;
  210. adjustForm.remark = "";
  211. addDialog({
  212. title: "库存调整",
  213. width: "30%",
  214. draggable: true,
  215. fullscreen: deviceDetection(),
  216. closeOnClickModal: false,
  217. contentRenderer: () => (
  218. <ElForm ref={ruleFormRef} model={adjustForm} label-width="100px">
  219. <ElFormItem label="当前库存">{row.stock}</ElFormItem>
  220. <ElFormItem label="调整后库存" prop="newStock" rules={[{ required: true, message: "请输入调整后库存", trigger: "blur" }]}>
  221. <ElInputNumber v-model={adjustForm.newStock} min={0} class="w-full!" />
  222. </ElFormItem>
  223. <ElFormItem label="备注" prop="remark">
  224. <ElInput v-model={adjustForm.remark} type="textarea" placeholder="请输入备注" rows={3} />
  225. </ElFormItem>
  226. </ElForm>
  227. ),
  228. beforeSure: async (done) => {
  229. const valid = await ruleFormRef.value.validate().catch(() => false);
  230. if (!valid) return;
  231. try {
  232. const res = await adjustStock(toRaw(adjustForm));
  233. if (res.code === 200) {
  234. message("库存调整成功", { type: "success" });
  235. done();
  236. onSearch();
  237. } else {
  238. message(res.message || "调整失败", { type: "error" });
  239. }
  240. } catch (error) {
  241. message("调整失败", { type: "error" });
  242. }
  243. }
  244. });
  245. }
  246. // 获取设备列表
  247. async function fetchDeviceOptions() {
  248. try {
  249. const { data } = await getDeviceList({ page: 1, pageSize: 1000 });
  250. deviceOptions.value = data.list || [];
  251. } catch (error) {
  252. console.error("获取设备列表失败:", error);
  253. }
  254. }
  255. // 获取商品列表
  256. async function fetchProductOptions() {
  257. try {
  258. const { data } = await getProductList({ page: 1, pageSize: 1000 });
  259. productOptions.value = data.list || [];
  260. } catch (error) {
  261. console.error("获取商品列表失败:", error);
  262. }
  263. }
  264. onMounted(async () => {
  265. await Promise.all([fetchDeviceOptions(), fetchProductOptions()]);
  266. onSearch();
  267. });
  268. return {
  269. form,
  270. loading,
  271. columns,
  272. dataList,
  273. pagination,
  274. deviceOptions,
  275. productOptions,
  276. onSearch,
  277. resetForm,
  278. handleStockIn,
  279. handleAdjust,
  280. handleSizeChange,
  281. handleCurrentChange
  282. };
  283. }