hook.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. import dayjs from "dayjs";
  2. import { message } from "@/utils/message";
  3. import type { PaginationProps } from "@pureadmin/table";
  4. import {
  5. getLayerTemplateList,
  6. deleteLayerTemplate,
  7. syncLayerTemplate,
  8. batchSyncLayerTemplates
  9. } from "@/api/layer-template";
  10. import { getDeviceList } from "@/api/device";
  11. import { ElMessageBox } from "element-plus";
  12. import { type Ref, ref, reactive, onMounted, h } from "vue";
  13. import { ElTag } from "element-plus";
  14. /**
  15. * 层模版搜索表单类型定义
  16. */
  17. export interface LayerTemplateSearchForm {
  18. /** 设备ID / Device ID */
  19. deviceId: string;
  20. /** 模板名称 / Template name */
  21. templateName: string;
  22. /** 同步状态 / Sync status */
  23. syncStatus: string | number;
  24. }
  25. /**
  26. * 层模版数据项类型
  27. */
  28. export interface LayerTemplateItem {
  29. /** 主键ID / Primary key ID */
  30. id?: number;
  31. /** 模版名称 / Template name */
  32. templateName?: string;
  33. /** 模版编码 / Template code */
  34. templateCode?: string;
  35. /** 设备ID / Device ID */
  36. deviceId?: number | string;
  37. /** 设备名称 / Device name */
  38. deviceName?: string;
  39. /** 总楼层数 / Total floor count */
  40. totalFloors?: number;
  41. /** 同步状态 (0-未同步, 1-同步中, 2-已同步, 3-同步失败) */
  42. syncStatus?: number;
  43. /** 最后同步时间 / Last sync time */
  44. lastSyncTime?: string;
  45. /** 状态 (0-禁用, 1-启用) */
  46. status?: number;
  47. /** 备注 / Remarks */
  48. remark?: string;
  49. /** 创建时间 / Create time */
  50. createTime?: string;
  51. /** 更新时间 / Update time */
  52. updateTime?: string;
  53. }
  54. /**
  55. * 设备选项类型(用于下拉选择)
  56. */
  57. export interface DeviceOption {
  58. /** 设备ID / Device ID (SN号) */
  59. deviceId: string;
  60. /** 设备名称/编码 / Device name or code */
  61. label: string;
  62. /** 门店名称 / Shop name */
  63. shopName?: string;
  64. /** 在线状态 / Online status */
  65. isOnline?: number;
  66. }
  67. export function useProduct(tableRef?: Ref) {
  68. // 搜索表单状态
  69. const form = reactive<LayerTemplateSearchForm>({
  70. deviceId: "",
  71. templateName: "",
  72. syncStatus: ""
  73. });
  74. // 数据列表状态
  75. const dataList = ref<LayerTemplateItem[]>([]);
  76. // 加载状态
  77. const loading = ref(false);
  78. // 分页配置
  79. const pagination = reactive<PaginationProps>({
  80. total: 0,
  81. pageSize: 10,
  82. currentPage: 1,
  83. background: true
  84. });
  85. // ========== 新增:设备列表相关状态 ==========
  86. // 设备选项列表(用于操作栏的下拉选择)
  87. const deviceOptions = ref<DeviceOption[]>([]);
  88. // 设备列表加载状态
  89. const deviceLoading = ref(false);
  90. // 当前选中的设备ID(用于单个设备同步)
  91. const selectedDeviceId = ref<string>("");
  92. // 全量同步加载状态
  93. const syncAllLoading = ref(false);
  94. // 同步状态选项
  95. const syncStatusOptions = [
  96. { label: "未同步", value: 0 },
  97. { label: "同步中", value: 1 },
  98. { label: "已同步", value: 2 },
  99. { label: "同步失败", value: 3 }
  100. ];
  101. // 表格列定义
  102. const columns: TableColumnList = [
  103. {
  104. label: "ID",
  105. prop: "id",
  106. width: 100
  107. },
  108. {
  109. label: "模板名称",
  110. prop: "templateName",
  111. minWidth: 100,
  112. showOverflowTooltip: true
  113. },
  114. {
  115. label: "设备ID",
  116. prop: "deviceId",
  117. minWidth: 100
  118. },
  119. {
  120. label: "机柜层数",
  121. prop: "totalFloors",
  122. width: 100,
  123. formatter: ({ totalFloors }) => (totalFloors != null ? `${totalFloors}层` : "-")
  124. },
  125. {
  126. label: "同步状态",
  127. prop: "syncStatus",
  128. width: 100,
  129. cellRenderer: ({ row }) => {
  130. const statusMap: Record<number, { label: string; type: string }> = {
  131. 0: { label: "未同步", type: "info" },
  132. 1: { label: "同步中", type: "warning" },
  133. 2: { label: "已同步", type: "success" },
  134. 3: { label: "同步失败", type: "danger" }
  135. };
  136. const status = statusMap[row.syncStatus] || {
  137. label: "未知",
  138. type: "info"
  139. };
  140. return h(ElTag, { type: status.type as any }, () => status.label);
  141. }
  142. },
  143. {
  144. label: "最后同步时间",
  145. prop: "lastSyncTime",
  146. minWidth: 160,
  147. formatter: ({ lastSyncTime }) =>
  148. lastSyncTime ? dayjs(lastSyncTime).format("YYYY-MM-DD HH:mm:ss") : "-"
  149. },
  150. {
  151. label: "创建时间",
  152. prop: "createTime",
  153. minWidth: 160,
  154. formatter: ({ createTime }) =>
  155. createTime ? dayjs(createTime).format("YYYY-MM-DD HH:mm:ss") : "-"
  156. },
  157. {
  158. label: "操作",
  159. fixed: "right",
  160. width: 200,
  161. slot: "operation"
  162. }
  163. ];
  164. /**
  165. * 加载设备列表(用于同步时选择设备)
  166. * Load device list for device selection during sync
  167. */
  168. async function loadDeviceList() {
  169. if (deviceLoading.value) return;
  170. deviceLoading.value = true;
  171. try {
  172. // 调用设备列表API获取所有设备
  173. // 注意:参数名必须是 pageSize(不是 limit)
  174. const res = await getDeviceList({ page: 1, pageSize: 9999 });
  175. console.log("设备API返回数据:", res); // 调试日志
  176. // 注意:后端成功响应的 code 是 200,不是 0
  177. if ((res.code === 200 || res.code === 0) && res.data?.list && Array.isArray(res.data.list)) {
  178. // 根据 Device 实体类的实际字段进行映射:
  179. // - deviceId: 设备ID/SN号
  180. // - name: 设备名称(注意不是 deviceName!)
  181. // - isOnline: Boolean 类型在线状态
  182. // - status: Integer 类型状态码
  183. deviceOptions.value = res.data.list
  184. .filter((device: any) => device && device.deviceId) // 过滤掉无效数据
  185. .map((device: any) => ({
  186. deviceId: String(device.deviceId || ""), // 确保转换为字符串
  187. label: device.shopName
  188. ? `${device.deviceId} — ${device.shopName}`
  189. : device.deviceId,
  190. shopName: device.shopName || "",
  191. isOnline: device.isOnline ?? (device.status === 1), // 兼容处理
  192. status: device.status // 保留原始状态值
  193. }));
  194. console.log("转换后的设备选项:", deviceOptions.value); // 调试日志
  195. if (deviceOptions.value.length === 0) {
  196. console.warn("设备列表为空,原始数据:", res.data.list);
  197. }
  198. } else {
  199. console.warn("获取设备列表返回异常:", res);
  200. deviceOptions.value = [];
  201. }
  202. } catch (error) {
  203. console.error("加载设备列表失败:", error);
  204. message("加载设备列表失败,请检查网络连接", { type: "warning" });
  205. deviceOptions.value = [];
  206. } finally {
  207. deviceLoading.value = false;
  208. }
  209. }
  210. /**
  211. * 搜索查询方法
  212. * Search query method
  213. */
  214. async function onSearch() {
  215. loading.value = true;
  216. try {
  217. const searchParams: any = {
  218. page: pagination.currentPage,
  219. pageSize: pagination.pageSize
  220. };
  221. // 只添加非空的搜索条件
  222. if (form.deviceId) searchParams.deviceId = form.deviceId.trim();
  223. if (form.templateName)
  224. searchParams.templateName = form.templateName.trim();
  225. if (form.syncStatus !== "" && form.syncStatus != null) {
  226. searchParams.syncStatus = parseInt(form.syncStatus.toString(), 10);
  227. }
  228. const { data } = await getLayerTemplateList(searchParams);
  229. dataList.value = data.list || [];
  230. pagination.total = Number(data.total) || 0;
  231. } catch (error) {
  232. console.error("获取层模版列表失败:", error);
  233. message("获取层模版列表失败", { type: "error" });
  234. } finally {
  235. setTimeout(() => {
  236. loading.value = false;
  237. }, 300);
  238. }
  239. }
  240. /**
  241. * 重置表单并重新查询
  242. * Reset form and re-query
  243. */
  244. function resetForm(formEl?: any) {
  245. if (!formEl) return;
  246. formEl.resetFields();
  247. pagination.currentPage = 1;
  248. onSearch();
  249. }
  250. /**
  251. * 分页大小改变处理
  252. * Handle page size change
  253. */
  254. function handleSizeChange(val: number) {
  255. pagination.pageSize = val;
  256. onSearch();
  257. }
  258. /**
  259. * 当前页码改变处理
  260. * Handle current page change
  261. */
  262. function handleCurrentChange(val: number) {
  263. pagination.currentPage = val;
  264. onSearch();
  265. }
  266. /**
  267. * 打开新增/编辑对话框
  268. * Open add/edit dialog
  269. * @param title - 对话框标题
  270. * @param row - 编辑时的行数据(可选)
  271. */
  272. function openDialog(title?: string, row?: LayerTemplateItem) {
  273. // 此方法将在index.vue中与EditDialog组件配合使用
  274. // 通过事件或ref调用EditDialog的打开方法
  275. console.log("openDialog called with:", title, row);
  276. // 触发自定义事件或通过其他方式通知父组件
  277. }
  278. /**
  279. * 删除层模版
  280. * Delete layer template
  281. * @param row - 要删除的行数据
  282. */
  283. async function handleDelete(row: LayerTemplateItem) {
  284. try {
  285. await ElMessageBox.confirm(
  286. `确认要删除模板 "${row.templateName}" 吗?`,
  287. "删除确认",
  288. {
  289. confirmButtonText: "确定",
  290. cancelButtonText: "取消",
  291. type: "warning"
  292. }
  293. );
  294. const res = await deleteLayerTemplate(row.id!);
  295. if (res.code === 200 || res.code === 0) {
  296. message(`成功删除模板 ${row.templateName}`, { type: "success" });
  297. onSearch();
  298. } else {
  299. message(res.message || "删除失败", { type: "error" });
  300. }
  301. } catch (error: any) {
  302. if (error !== "cancel") {
  303. message("删除操作失败", { type: "error" });
  304. }
  305. }
  306. }
  307. /**
  308. * 根据选择的设备ID同步单个设备的层模版
  309. * Sync layer template for a specific selected device
  310. * @param deviceId - 设备ID(从下拉框选择的)
  311. */
  312. async function handleSyncByDevice(deviceId: string) {
  313. if (!deviceId) {
  314. message("请先选择要同步的设备", { type: "warning" });
  315. return;
  316. }
  317. try {
  318. // 查找设备显示名称
  319. const device = deviceOptions.value.find(d => d.deviceId === deviceId);
  320. const deviceLabel = device?.label || deviceId;
  321. await ElMessageBox.confirm(
  322. `确认要同步设备 "${deviceLabel}" 的层模版信息吗?`,
  323. "同步确认",
  324. {
  325. confirmButtonText: "确定",
  326. cancelButtonText: "取消",
  327. type: "warning"
  328. }
  329. );
  330. const res = await syncLayerTemplate(deviceId);
  331. if (res.code === 200 || res.code === 0) {
  332. message(`已提交设备 "${deviceLabel}" 的同步请求`, { type: "success" });
  333. // 延迟刷新以获取最新状态
  334. setTimeout(() => {
  335. onSearch();
  336. }, 2000);
  337. } else {
  338. message(res.message || "同步失败", { type: "error" });
  339. }
  340. } catch (error: any) {
  341. if (error !== "cancel") {
  342. console.error("同步失败:", error);
  343. message("同步操作失败", { type: "error" });
  344. }
  345. }
  346. }
  347. /**
  348. * 全量同步所有设备的层模版(默认行为)
  349. * Batch sync all devices' layer templates (default behavior)
  350. *
  351. * 流程:
  352. * 1. 从设备列表获取所有设备的deviceId
  353. * 2. 批量调用同步接口
  354. */
  355. async function handleSyncAll() {
  356. console.log("开始全量同步, 当前设备列表长度:", deviceOptions.value.length);
  357. // 如果设备列表还没加载或为空,先加载
  358. if (deviceOptions.value.length === 0) {
  359. console.log("设备列表为空, 尝试重新加载...");
  360. await loadDeviceList();
  361. }
  362. // 再次检查
  363. if (deviceOptions.value.length === 0) {
  364. console.error("全量同步失败: 设备列表仍为空");
  365. message(
  366. "没有可用的设备数据,无法执行全量同步\n\n可能原因:\n1. 数据库中没有设备记录\n2. 设备列表API返回异常\n3. 网络连接问题",
  367. { type: "warning", duration: 5000 }
  368. );
  369. return;
  370. }
  371. try {
  372. const deviceCount = deviceOptions.value.length;
  373. await ElMessageBox.confirm(
  374. `确认要同步全部 ${deviceCount} 个设备的层模版信息吗?\n\n此操作将从哈哈零兽平台拉取每个设备的最新层模版配置。\n\n预计耗时:${Math.ceil(deviceCount / 10)} 秒`,
  375. "全量同步确认",
  376. {
  377. confirmButtonText: "确定",
  378. cancelButtonText: "取消",
  379. type: "warning"
  380. }
  381. );
  382. syncAllLoading.value = true;
  383. // 提取所有设备的deviceId(确保都是非空字符串)
  384. const allDeviceIds = deviceOptions.value
  385. .map(d => d.deviceId)
  386. .filter(id => id && id.trim() !== ""); // 过滤空值
  387. console.log("准备同步的设备ID列表:", allDeviceIds);
  388. if (allDeviceIds.length === 0) {
  389. message("所有设备的ID都为空,无法执行同步", { type: "error" });
  390. return;
  391. }
  392. const res = await batchSyncLayerTemplates(allDeviceIds);
  393. console.log("批量同步API返回:", res);
  394. // 注意:后端成功响应的 code 是 200,不是 0
  395. if (res.code === 200 || res.code === 0) {
  396. message(`成功提交 ${allDeviceIds.length} 个设备的同步请求`, {
  397. type: "success"
  398. });
  399. // 延迟刷新以获取最新状态
  400. setTimeout(() => {
  401. onSearch();
  402. }, 3000); // 全量同步等待更长时间
  403. } else {
  404. message(res.message || "全量同步失败", { type: "error" });
  405. }
  406. } catch (error: any) {
  407. if (error !== "cancel") {
  408. console.error("全量同步失败:", error);
  409. message("全量同步操作失败: " + (error.message || "未知错误"), { type: "error" });
  410. }
  411. } finally {
  412. syncAllLoading.value = false;
  413. }
  414. }
  415. // 组件挂载时自动加载数据和设备列表
  416. onMounted(async () => {
  417. // 并行加载层模版列表和设备列表
  418. await Promise.all([onSearch(), loadDeviceList()]);
  419. });
  420. return {
  421. // 状态
  422. form,
  423. loading,
  424. dataList,
  425. pagination,
  426. columns,
  427. syncStatusOptions,
  428. // 设备相关状态(新增)
  429. deviceOptions,
  430. deviceLoading,
  431. selectedDeviceId,
  432. syncAllLoading,
  433. // 方法
  434. onSearch,
  435. resetForm,
  436. openDialog,
  437. handleDelete,
  438. // 同步相关方法(重构)
  439. handleSyncByDevice, // 选择单个设备同步
  440. handleSyncAll, // 全量同步所有设备
  441. // 兼容旧接口(保持向后兼容)
  442. handleSync: handleSyncByDevice,
  443. handleBatchSync: handleSyncAll,
  444. // 分页方法
  445. handleSizeChange,
  446. handleCurrentChange,
  447. // 工具方法
  448. loadDeviceList // 手动刷新设备列表
  449. };
  450. }