| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506 |
- import dayjs from "dayjs";
- import { message } from "@/utils/message";
- import { addDialog } from "@/components/ReDialog";
- import type { PaginationProps } from "@pureadmin/table";
- import { deviceDetection } from "@pureadmin/utils";
- import {
- getDeviceList,
- openDoor,
- setTemperature,
- setVolume,
- getDoorRecords,
- type DoorRecordItem
- } from "@/api/device";
- import { getEnabledShops } from "@/api/shop";
- import { type Ref, ref, toRaw, reactive, onMounted } from "vue";
- import {
- ElForm,
- ElFormItem,
- ElInput,
- ElInputNumber,
- ElSelect,
- ElOption,
- ElMessageBox
- } from "element-plus";
- import type { DeviceItem, DeviceSearchForm } from "./types";
- export function useDevice(tableRef: Ref) {
- const form = reactive<DeviceSearchForm>({
- deviceId: "",
- shopId: "",
- status: "",
- storeName: ""
- });
- const formRef = ref();
- const ruleFormRef = ref();
- const dataList = ref([]);
- const loading = ref(true);
- const shopOptions = ref([]);
- // 操作按钮的加载状态
- const operatingIds = ref<Set<number>>(new Set());
-
- // 开关门记录相关
- const dialogVisible = ref(false);
- const recordLoading = ref(false);
- const recordList = ref<DoorRecordItem[]>([]);
- const currentDeviceId = ref<string>("");
- const recordPagination = reactive<PaginationProps>({
- total: 0,
- pageSize: 10,
- currentPage: 1,
- background: true
- });
-
- const pagination = reactive<PaginationProps>({
- total: 0,
- pageSize: 10,
- currentPage: 1,
- background: true
- });
- const columns: TableColumnList = [
- {
- label: "设备ID",
- prop: "deviceId",
- minWidth: 120
- },
- {
- label: "设备名称",
- prop: "deviceName",
- minWidth: 120
- },
- {
- label: "所属门店",
- prop: "shopName",
- minWidth: 120
- },
- {
- label: "货柜名称",
- prop: "storeName",
- minWidth: 100
- },
- {
- label: "温度(℃)",
- prop: "temperature",
- minWidth: 80
- },
- {
- label: "音量",
- prop: "volume",
- minWidth: 60
- },
- {
- label: "门状态",
- prop: "doorStatus",
- minWidth: 80,
- cellRenderer: ({ row }) => (
- <el-tag type={row.doorStatus === 1 ? "warning" : "success"}>
- {row.doorStatus === 1 ? "已开门" : "已关门"}
- </el-tag>
- )
- },
- {
- label: "状态",
- prop: "status",
- minWidth: 80,
- cellRenderer: ({ row }) => (
- <el-tag type={row.status === 1 ? "success" : "danger"}>
- {row.status === 1 ? "在线" : "离线"}
- </el-tag>
- )
- },
- {
- label: "最后在线时间",
- prop: "lastOnlineTime",
- minWidth: 160,
- formatter: ({ lastOnlineTime }) =>
- lastOnlineTime ? dayjs(lastOnlineTime).format("YYYY-MM-DD HH:mm:ss") : "-"
- },
- {
- label: "操作",
- fixed: "right",
- width: 200,
- slot: "operation"
- }
- ];
- // 搜索
- async function onSearch() {
- loading.value = true;
- try {
- const searchParams: any = {
- page: pagination.currentPage,
- pageSize: pagination.pageSize
- };
-
- // 只添加非空的搜索条件
- if (form.deviceId) searchParams.deviceId = form.deviceId;
- if (form.shopId) searchParams.shopId = form.shopId;
- if (form.status) searchParams.status = form.status;
- if (form.storeName) searchParams.storeName = form.storeName;
-
- const { data } = await getDeviceList(searchParams);
- dataList.value = data.list;
- pagination.total = data.total;
- } catch (error) {
- console.error("获取设备列表失败:", error);
- dataList.value = [];
- pagination.total = 0;
- } finally {
- setTimeout(() => {
- loading.value = false;
- }, 300);
- }
- }
- // 重置表单
- const resetForm = formEl => {
- if (!formEl) return;
- formEl.resetFields();
- pagination.currentPage = 1;
- onSearch();
- };
- // 分页
- function handleSizeChange(val: number) {
- pagination.pageSize = val;
- onSearch();
- }
- function handleCurrentChange(val: number) {
- pagination.currentPage = val;
- onSearch();
- }
- // 远程开门
- async function handleOpenDoor(row: DeviceItem) {
- try {
- const deviceName = row.deviceName || row.storeName || `设备${row.deviceId}`;
-
- await ElMessageBox.confirm(`确认要远程开启 ${deviceName} 的门吗?`, "系统提示", {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- type: "warning"
- });
-
- // 添加 loading 状态
- operatingIds.value.add(row.id);
-
- // 传递 doorIndex 参数,默认为 A 门
- const res = await openDoor(row.id, { doorIndex: "A" });
- if (res.code === 0) {
- message(`已发送开门指令到设备 ${deviceName}`, { type: "success" });
- // 开门成功后刷新列表,更新门状态
- await onSearch();
- } else {
- message(res.message || "开门失败", { type: "error" });
- }
- } catch (error) {
- // 用户取消或请求失败
- if (error !== "cancel") {
- console.error("开门失败:", error);
- message("操作失败", { type: "error" });
- }
- } finally {
- // 移除 loading 状态
- operatingIds.value.delete(row.id);
- }
- }
- // 设置温度
- const tempForm = reactive({ temperature: 0 });
-
- function handleSetTemperature(row: DeviceItem) {
- tempForm.temperature = row.temperature;
-
- addDialog({
- title: `设置 ${row.deviceName} 的温度`,
- width: "30%",
- draggable: true,
- closeOnClickModal: false,
- fullscreen: deviceDetection(),
- contentRenderer: () => (
- <ElForm ref={ruleFormRef} model={tempForm} label-width="80px">
- <ElFormItem
- label="温度 (℃)"
- prop="temperature"
- rules={[
- { required: true, message: "请输入温度", trigger: "blur" },
- {
- type: "number",
- min: -30,
- max: 30,
- message: "温度范围为 -30℃ 到 30℃",
- trigger: "blur"
- }
- ]}
- >
- <ElInputNumber
- v-model={tempForm.temperature}
- min={-30}
- max={30}
- step={0.5}
- precision={1}
- class="w-full!"
- placeholder="请输入温度值"
- />
- </ElFormItem>
- </ElForm>
- ),
- beforeSure: async (done) => {
- try {
- // 添加 loading 状态
- operatingIds.value.add(row.id);
-
- const res = await setTemperature(row.id, { temperature: tempForm.temperature });
- if (res.code === 0) {
- message(`已设置温度为 ${tempForm.temperature}℃`, { type: "success" });
- done();
- // 温度设置成功后刷新列表,更新温度显示
- await onSearch();
- } else {
- message(res.message || "设置失败", { type: "error" });
- }
- } catch (error) {
- console.error("设置温度失败:", error);
- message("设置失败", { type: "error" });
- } finally {
- // 移除 loading 状态
- operatingIds.value.delete(row.id);
- }
- }
- });
- }
- // 设置音量
- const volumeForm = reactive({ volume: 0 });
- function handleSetVolume(row: DeviceItem) {
- volumeForm.volume = row.volume;
- addDialog({
- title: `设置 ${row.deviceName} 的音量`,
- width: "30%",
- draggable: true,
- closeOnClickModal: false,
- fullscreen: deviceDetection(),
- contentRenderer: () => (
- <ElForm ref={ruleFormRef} model={volumeForm} label-width="80px">
- <ElFormItem
- label="音量"
- prop="volume"
- rules={[
- { required: true, message: "请输入音量", trigger: "blur" },
- {
- type: "number",
- min: 0,
- max: 100,
- message: "音量范围为 0 到 100",
- trigger: "blur"
- }
- ]}
- >
- <ElInputNumber
- v-model={volumeForm.volume}
- min={0}
- max={100}
- step={5}
- class="w-full!"
- placeholder="请输入音量值 (0-100)"
- />
- </ElFormItem>
- </ElForm>
- ),
- beforeSure: async (done) => {
- try {
- // 添加 loading 状态
- operatingIds.value.add(row.id);
-
- const res = await setVolume(row.id, { volume: volumeForm.volume });
- if (res.code === 0) {
- message(`已设置音量为 ${volumeForm.volume}`, { type: "success" });
- done();
- // 音量设置成功后刷新列表,更新音量显示
- await onSearch();
- } else {
- message(res.message || "设置失败", { type: "error" });
- }
- } catch (error) {
- console.error("设置音量失败:", error);
- message("设置失败", { type: "error" });
- } finally {
- // 移除 loading 状态
- operatingIds.value.delete(row.id);
- }
- }
- });
- }
- const recordColumns: TableColumnList = [
- {
- label: "活动 ID",
- prop: "activityId",
- minWidth: 180
- },
- {
- label: "用户 ID",
- prop: "userId",
- minWidth: 100
- },
- {
- label: "门索引",
- prop: "doorIndex",
- minWidth: 80
- },
- {
- label: "类型",
- prop: "openType",
- minWidth: 80,
- cellRenderer: ({ row }) => (
- <el-tag type={row.openType === "IN" ? "warning" : "info"}>
- {row.openType === "IN" ? "上货" : "消费"}
- </el-tag>
- )
- },
- {
- label: "门状态",
- prop: "doorStatus",
- minWidth: 100,
- cellRenderer: ({ row }) => (
- <el-tag
- type={
- row.doorStatus === "OPENED" ? "danger" :
- row.doorStatus === "CLOSED" ? "success" : "info"
- }
- >
- {
- row.doorStatus === "OPENED" ? "已开门" :
- row.doorStatus === "CLOSED" ? "已关门" : "异常"
- }
- </el-tag>
- )
- },
- {
- label: "是否有消费",
- prop: "nobuy",
- minWidth: 100,
- cellRenderer: ({ row }) => (
- <el-tag type={row.nobuy === 0 ? "success" : "info"}>
- {row.nobuy === 0 ? "有消费" : "无消费"}
- </el-tag>
- )
- },
- {
- label: "开门时间",
- prop: "openTime",
- minWidth: 160,
- formatter: ({ openTime }) => dayjs(openTime).format("YYYY-MM-DD HH:mm:ss")
- },
- {
- label: "关门时间",
- prop: "closeTime",
- minWidth: 160,
- formatter: ({ closeTime }) =>
- closeTime ? dayjs(closeTime).format("YYYY-MM-DD HH:mm:ss") : "-"
- },
- {
- label: "持续时长 (秒)",
- prop: "duration",
- minWidth: 100
- },
- {
- label: "来源",
- prop: "source",
- minWidth: 100,
- cellRenderer: ({ row }) => (
- <el-tag type={row.source === "MINIAPP" ? "primary" : "info"} size="small">
- {row.source === "MINIAPP" ? "小程序" : row.source === "ADMIN" ? "管理后台" : row.source || "-"}
- </el-tag>
- )
- }
- ];
-
- // 查询开关门记录
- async function fetchDoorRecords() {
- if (!currentDeviceId.value) return;
-
- recordLoading.value = true;
- try {
- const res = await getDoorRecords(currentDeviceId.value, {
- page: recordPagination.currentPage,
- pageSize: recordPagination.pageSize
- });
-
- if (res.code === 0 && res.data) {
- recordList.value = res.data.list || [];
- recordPagination.total = res.data.total || 0;
- }
- } catch (error) {
- console.error("查询开关门记录失败:", error);
- message("查询失败", { type: "error" });
- } finally {
- recordLoading.value = false;
- }
- }
-
- // 显示开关门记录弹窗
- function showDoorRecords(row: DeviceItem) {
- currentDeviceId.value = row.deviceId;
- recordPagination.currentPage = 1;
- recordPagination.pageSize = 10;
- dialogVisible.value = true;
- fetchDoorRecords();
- }
-
- // 记录分页大小变化
- function handleRecordSizeChange(val: number) {
- recordPagination.pageSize = val;
- recordPagination.currentPage = 1;
- fetchDoorRecords();
- }
-
- // 记录页码变化
- function handleRecordCurrentChange(val: number) {
- recordPagination.currentPage = val;
- fetchDoorRecords();
- }
- async function fetchShopOptions() {
- try {
- const { data } = await getEnabledShops();
- shopOptions.value = data || [];
- } catch (error) {
- console.error("获取门店列表失败:", error);
- }
- }
- onMounted(async () => {
- await fetchShopOptions();
- onSearch();
- });
- return {
- form,
- loading,
- columns,
- dataList,
- pagination,
- shopOptions,
- onSearch,
- resetForm,
- handleOpenDoor,
- handleSetTemperature,
- handleSetVolume,
- handleSizeChange,
- handleCurrentChange,
- operatingIds,
- // 开关门记录相关
- dialogVisible,
- recordLoading,
- recordList,
- recordColumns,
- recordPagination,
- showDoorRecords,
- handleRecordSizeChange,
- handleRecordCurrentChange
- };
- }
|