gaoluyang
9 天以前 fbb2c7eb5ac25ddc7d67604e07b0fa333bb60b91
采购台账扫码采集、韦德实业的设备监控
已修改1个文件
已添加1个文件
666 ■■■■■ 文件已修改
src/views/equipmentManagement/iotMonitor/indexWD.vue 317 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/index.vue 349 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/iotMonitor/indexWD.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,317 @@
<template>
  <div class="app-container iot-monitor">
    <div class="header">
      <div class="title">实时工况监控(IoT)</div>
      <div class="actions">
        <el-button type="primary" @click="toggleCollecting">{{ collecting ? '暂停采集' : '启动采集' }}</el-button>
        <el-button @click="resetAll">重置</el-button>
        <span class="ts">上次更新时间:{{ lastUpdatedDisplay }}</span>
      </div>
    </div>
<!--    <el-alert-->
<!--      title="边缘预警规则:轴承磨损-振动值偏离基线±5%触发告警;温度/压力越界触发提醒"-->
<!--      type="info"-->
<!--      :closable="false"-->
<!--      show-icon-->
<!--      class="rule-alert"-->
<!--    />-->
    <el-row :gutter="16">
      <el-col v-for="dev in devices" :key="dev.id" :span="12">
        <el-card :class="['device-card', dev.hasAlert ? 'is-alert' : '']">
          <template #header>
            <div class="card-header">
              <div class="card-title">
                <span class="device-name">{{ dev.name }}</span>
                <el-tag :type="dev.hasAlert ? 'danger' : 'success'" size="small">{{ dev.hasAlert ? '告警' : '正常' }}</el-tag>
              </div>
              <div class="meta">类型:{{ dev.type }}|基线振动:{{ dev.baseline.vibration.toFixed(2) }} mm/s</div>
            </div>
          </template>
          <div class="metrics">
            <div class="metric" :class="{ 'metric-alert': dev.alerts.vibration }">
              <div class="metric-head">
                <span>振动(mm/s)</span>
                <el-tag :type="dev.alerts.vibration ? 'danger' : 'info'" size="small">{{ dev.alerts.vibration ? '±5%越界' : '基线±5%' }}</el-tag>
              </div>
              <div class="metric-value">{{ currentValue(dev.series.vibration).toFixed(2) }}</div>
              <Echarts
                :xAxis="[{ type: 'category', data: xAxisLabels }]"
                :yAxis="[{ type: 'value', name: 'mm/s' }]"
                :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.vibration }]"
                :tooltip="{ trigger: 'axis' }"
                :grid="{ left: 40, right: 10, top: 10, bottom: 20 }"
                :chartStyle="{ height: '160px', width: '100%' }"
                :lineColors="['#409EFF']"
              />
            </div>
            <div class="metric" :class="{ 'metric-alert': dev.alerts.temperature }">
              <div class="metric-head">
                <span>温度(°C)</span>
                <el-tag :type="dev.alerts.temperature ? 'warning' : 'info'" size="small">{{ dev.alerts.temperature ? '越界' : '20~80' }}</el-tag>
              </div>
              <div class="metric-value">{{ currentValue(dev.series.temperature).toFixed(1) }}</div>
              <Echarts
                :xAxis="[{ type: 'category', data: xAxisLabels }]"
                :yAxis="[{ type: 'value', name: '°C' }]"
                :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.temperature }]"
                :tooltip="{ trigger: 'axis' }"
                :grid="{ left: 40, right: 10, top: 10, bottom: 20 }"
                :chartStyle="{ height: '160px', width: '100%' }"
                :lineColors="['#E6A23C']"
              />
            </div>
            <div class="metric" :class="{ 'metric-alert': dev.alerts.pressure }">
              <div class="metric-head">
                <span>压力(MPa)</span>
                <el-tag :type="dev.alerts.pressure ? 'warning' : 'info'" size="small">{{ dev.alerts.pressure ? '越界' : '0.2~1.5' }}</el-tag>
              </div>
              <div class="metric-value">{{ currentValue(dev.series.pressure).toFixed(2) }}</div>
              <Echarts
                :xAxis="[{ type: 'category', data: xAxisLabels }]"
                :yAxis="[{ type: 'value', name: 'MPa' }]"
                :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.pressure }]"
                :tooltip="{ trigger: 'axis' }"
                :grid="{ left: 40, right: 10, top: 10, bottom: 20 }"
                :chartStyle="{ height: '160px', width: '100%' }"
                :lineColors="['#67C23A']"
              />
            </div>
          </div>
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue'
import { ElNotification } from 'element-plus'
import Echarts from '@/components/Echarts/echarts.vue'
defineOptions({ name: 'IoTMonitor' })
const windowSize = 30
const collecting = ref(true)
const lastUpdated = ref(Date.now())
const lastUpdatedDisplay = computed(() => new Date(lastUpdated.value).toLocaleTimeString())
const xAxisLabels = ref(Array.from({ length: windowSize }, (_, i) => i - (windowSize - 1)).map(n => `${n}s`))
function makeSeries(fill, decimals = 2) {
  return Array.from({ length: windowSize }, () => Number(fill.toFixed(decimals)))
}
const devices = reactive([
  {
    id: 'hydrocyclone-desander',
    name: '旋流除砂器',
    type: '分离设备',
    baseline: { vibration: 8 },
    initial: { temperature: 35, pressure: 0.85 },
    alerts: { vibration: false, temperature: false, pressure: false },
    hasAlert: false,
    series: {
      vibration: makeSeries(8),
      temperature: makeSeries(35, 1),
      pressure: makeSeries(0.85, 2),
    },
  },
  {
    id: 'high-pressure-separator',
    name: '高压分离器撬',
    type: '分离设备',
    baseline: { vibration: 6 },
    initial: { temperature: 45, pressure: 1.20 },
    alerts: { vibration: false, temperature: false, pressure: false },
    hasAlert: false,
    series: {
      vibration: makeSeries(6),
      temperature: makeSeries(45, 1),
      pressure: makeSeries(1.2, 2),
    },
  },
  {
    id: 'heating-throttle-pressure',
    name: '组合式加热节流调压',
    type: '调压设备',
    baseline: { vibration: 10 },
    initial: { temperature: 75, pressure: 1.80 },
    alerts: { vibration: false, temperature: false, pressure: false },
    hasAlert: false,
    series: {
      vibration: makeSeries(10),
      temperature: makeSeries(75, 1),
      pressure: makeSeries(1.8, 2),
    },
  },
  {
    id: 'three-phase-separator',
    name: '三相分离器',
    type: '分离设备',
    baseline: { vibration: 7 },
    initial: { temperature: 38, pressure: 0.95 },
    alerts: { vibration: false, temperature: false, pressure: false },
    hasAlert: false,
    series: {
      vibration: makeSeries(7),
      temperature: makeSeries(38, 1),
      pressure: makeSeries(0.95, 2),
    },
  },
])
function currentValue(arr) {
  return arr[arr.length - 1] ?? 0
}
function pushWindow(arr, val) {
  if (arr.length >= windowSize) arr.shift()
  arr.push(val)
}
function clamp(val, min, max) { return Math.max(min, Math.min(max, val)) }
function tickDevice(dev) {
  const vibBase = dev.baseline.vibration
  // æŒ¯åŠ¨ï¼šåŸºçº¿Â±2%随机波动;5%概率触发8%~12%尖峰模拟告警
  const spike = Math.random() < 0.05
  const vibNoise = vibBase * (spike ? (1 + (Math.random() * 0.08 + 0.04) * (Math.random() < 0.5 ? -1 : 1)) : (1 + (Math.random() - 0.5) * 0.04))
  const vibVal = Number(vibNoise.toFixed(2))
  pushWindow(dev.series.vibration, vibVal)
  // æ¸©åº¦ï¼šç¼“慢随机游走,并添加偶发高温偏移
  const tPrev = currentValue(dev.series.temperature)
  const tDrift = tPrev + (Math.random() - 0.5) * 0.8 + (Math.random() < 0.02 ? 6 : 0)
  const tVal = Number(clamp(tDrift, 15, 95).toFixed(1))
  pushWindow(dev.series.temperature, tVal)
  // åŽ‹åŠ›ï¼šå°å¹…æ³¢åŠ¨ï¼Œå¶å‘ä½ŽåŽ‹/高压
  const pPrev = currentValue(dev.series.pressure)
  const pDrift = pPrev + (Math.random() - 0.5) * 0.05 + (Math.random() < 0.02 ? (Math.random() < 0.5 ? -0.3 : 0.3) : 0)
  const pVal = Number(clamp(pDrift, 0.05, 2.0).toFixed(2))
  pushWindow(dev.series.pressure, pVal)
  // è¾¹ç¼˜è®¡ç®—阈值判断
  const vibDelta = Math.abs(vibVal - vibBase) / vibBase
  const vibAlert = vibDelta > 0.05
  const tAlert = tVal < 20 || tVal > 80
  const pAlert = pVal < 0.2 || pVal > 1.5
  const prevHasAlert = dev.hasAlert
  dev.alerts.vibration = vibAlert
  dev.alerts.temperature = tAlert
  dev.alerts.pressure = pAlert
  dev.hasAlert = vibAlert || tAlert || pAlert
  if (dev.hasAlert && !prevHasAlert) {
    const reasons = []
    if (vibAlert) reasons.push(`振动偏离±5% (当前 ${vibVal} / åŸºçº¿ ${vibBase})`)
    if (tAlert) reasons.push(`温度越界 (当前 ${tVal}°C, æœŸæœ› 20~80°C) `)
    if (pAlert) reasons.push(`压力越界 (当前 ${pVal}MPa, æœŸæœ› 0.2~1.5MPa) `)
    ElNotification({
      title: `${dev.name} å‘Šè­¦`,
      message: reasons.join(';'),
      type: vibAlert ? 'error' : 'warning',
      duration: 5000,
    })
  }
}
let timer = null
function start() {
  if (timer) return
  timer = setInterval(() => {
    if (!collecting.value) return
    devices.forEach(tickDevice)
    lastUpdated.value = Date.now()
  }, 10000)
}
function stop() {
  if (timer) {
    clearInterval(timer)
    timer = null
  }
}
function toggleCollecting() { collecting.value = !collecting.value }
function resetAll() {
  devices.forEach(dev => {
    dev.series.vibration = makeSeries(dev.baseline.vibration)
    const t0 = dev.initial?.temperature ?? 45
    const p0 = dev.initial?.pressure ?? 0.8
    dev.series.temperature = makeSeries(t0, 1)
    dev.series.pressure = makeSeries(p0, 2)
    dev.alerts.vibration = false
    dev.alerts.temperature = false
    dev.alerts.pressure = false
    dev.hasAlert = false
  })
  lastUpdated.value = Date.now()
}
onMounted(() => {
  start()
})
onBeforeUnmount(() => {
  stop()
})
</script>
<style lang="scss" scoped>
.iot-monitor {
  .header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 12px;
    .title { font-size: 18px; font-weight: 600; }
    .actions { display: flex; align-items: center; gap: 8px; }
    .ts { color: #909399; font-size: 12px; }
  }
  .rule-alert { margin-bottom: 12px; }
}
.device-card {
  margin-bottom: 16px;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
  &.is-alert { border-color: #F56C6C; box-shadow: 0 0 0 2px rgba(245,108,108,0.2) inset; }
  .card-header {
    display: flex; flex-direction: column; gap: 4px;
    .card-title { display: flex; align-items: center; gap: 8px; font-weight: 600; }
    .meta { color: #909399; font-size: 12px; }
  }
  .metrics {
    display: grid;
    grid-template-columns: 1fr;
    gap: 12px;
  }
}
.metric {
  border: 1px solid #ebeef5;
  border-radius: 6px;
  padding: 8px 8px 0 8px;
  &-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; font-size: 13px; color: #606266; }
  &-value { font-size: 20px; font-weight: 600; margin: 2px 0 6px 0; }
}
.metric-alert {
  border-color: #F56C6C;
  background: #FFF6F6;
}
@media (min-width: 1200px) {
  .device-card .metrics { grid-template-columns: 1fr 1fr 1fr; }
}
</style>
src/views/procurementManagement/procurementLedger/index.vue
@@ -39,6 +39,7 @@
    <div class="table_list">
      <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;">
        <el-button type="primary" @click="openForm('add')">新增台账</el-button>
        <el-button type="success" @click="openScanAddDialog">扫码新增</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
@@ -164,6 +165,7 @@
              @click="showQRCode(scope.row)"
              >生成二维码</el-button
            >
          </template>
        </el-table-column>
      </el-table>
@@ -560,6 +562,206 @@
          <el-button type="primary" @click="downloadQRCode">下载二维码图片</el-button>
        </div>
      </div>
    </el-dialog>
    <!-- æ‰«ç æ–°å¢žå¯¹è¯æ¡† -->
    <el-dialog
      v-model="scanAddDialogVisible"
      title="扫码新增采购台账"
      width="70%"
      @close="closeScanAddDialog"
    >
      <el-form
        :model="scanAddForm"
        label-width="140px"
        label-position="top"
        :rules="scanAddRules"
        ref="scanAddFormRef"
      >
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="扫码内容:">
              <el-input
                v-model="scanAddForm.scanContent"
                type="textarea"
                :rows="3"
                placeholder="请扫描二维码或手动输入采购合同信息"
                @input="parseScanContent"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="采购合同号:" prop="purchaseContractNumber">
              <el-input
                v-model="scanAddForm.purchaseContractNumber"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="供应商名称:" prop="supplierName">
              <el-input
                v-model="scanAddForm.supplierName"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="项目名称:" prop="projectName">
              <el-input
                v-model="scanAddForm.projectName"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="合同金额(元):" prop="contractAmount">
              <el-input-number
                v-model="scanAddForm.contractAmount"
                :precision="2"
                :step="0.1"
                clearable
                style="width: 100%"
                placeholder="请输入"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="付款方式:">
              <el-input
                v-model="scanAddForm.paymentMethod"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="录入人:">
              <el-input v-model="scanAddForm.recorderName" disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注:">
              <el-input
                v-model="scanAddForm.remark"
                type="textarea"
                :rows="2"
                placeholder="请输入备注信息"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitScanAdd">确认新增</el-button>
          <el-button @click="closeScanAddDialog">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æ‰«ç ç™»è®°å¯¹è¯æ¡† -->
    <el-dialog
      v-model="scanDialogVisible"
      title="扫码登记"
      width="60%"
      @close="closeScanDialog"
    >
      <el-form
        :model="scanForm"
        label-width="120px"
        label-position="left"
        :rules="scanRules"
        ref="scanFormRef"
      >
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="采购合同号:">
              <el-input v-model="scanForm.purchaseContractNumber" disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="供应商名称:">
              <el-input v-model="scanForm.supplierName" disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="项目名称:">
              <el-input v-model="scanForm.projectName" disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="扫码时间:">
              <el-input v-model="scanForm.scanTime" disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="扫码人:">
              <el-input v-model="scanForm.scannerName" disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="扫码状态:">
              <el-tag :type="scanForm.scanStatus === '已扫码' ? 'success' : 'warning'">
                {{ scanForm.scanStatus }}
              </el-tag>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="扫码备注:">
              <el-input
                v-model="scanForm.scanRemark"
                type="textarea"
                :rows="3"
                placeholder="请输入扫码备注信息"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="扫码记录:">
              <el-table :data="scanRecords" border style="width: 100%">
                <el-table-column label="序号" type="index" width="60" align="center" />
                <el-table-column label="扫码时间" prop="scanTime" width="180" />
                <el-table-column label="扫码人" prop="scannerName" width="120" />
                <el-table-column label="扫码状态" prop="scanStatus" width="100">
                  <template #default="scope">
                    <el-tag :type="scope.row.scanStatus === '已扫码' ? 'success' : 'warning'">
                      {{ scope.row.scanStatus }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column label="备注" prop="scanRemark" />
              </el-table>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitScan">确认扫码</el-button>
          <el-button @click="closeScanDialog">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
@@ -1220,6 +1422,153 @@
  proxy.$modal.msgSuccess("下载成功");
};
// æ‰«ç æ–°å¢žå¯¹è¯æ¡†ç›¸å…³å˜é‡
const scanAddDialogVisible = ref(false);
const scanAddForm = reactive({
  scanContent: "",
  purchaseContractNumber: "",
  supplierName: "",
  projectName: "",
  contractAmount: "",
  paymentMethod: "",
  recorderName: "",
  scanRemark: "",
});
const scanAddRules = {
  purchaseContractNumber: [{ required: true, message: "请输入采购合同号", trigger: "blur" }],
  supplierName: [{ required: true, message: "请输入供应商名称", trigger: "blur" }],
  projectName: [{ required: true, message: "请输入项目名称", trigger: "blur" }],
};
// æ‰«ç ç™»è®°å¯¹è¯æ¡†ç›¸å…³å˜é‡
const scanDialogVisible = ref(false);
const scanForm = reactive({
  purchaseContractNumber: "",
  supplierName: "",
  projectName: "",
  scanTime: "",
  scannerName: "",
  scanStatus: "未扫码",
  scanRemark: "",
});
const scanRules = {
  scanRemark: [{ required: true, message: "请输入扫码备注", trigger: "blur" }],
};
const scanRecords = ref([]);
// æ‰“开扫码新增对话框
const openScanAddDialog = () => {
  scanAddForm.scanContent = "";
  scanAddForm.purchaseContractNumber = "";
  scanAddForm.supplierName = "";
  scanAddForm.projectName = "";
  scanAddForm.contractAmount = "";
  scanAddForm.paymentMethod = "";
  scanAddForm.recorderName = userStore.nickName;
  scanAddForm.scanRemark = "";
  scanAddDialogVisible.value = true;
};
// è§£æžæ‰«ç å†…容(模拟解析二维码数据)
const parseScanContent = (content) => {
  if (!content) return;
  // æ¨¡æ‹Ÿè§£æžäºŒç»´ç å†…容,这里可以根据实际需求调整解析逻辑
  // å‡è®¾æ‰«ç å†…容格式为:合同号|供应商|项目|金额|付款方式
  const parts = content.split('|');
  if (parts.length >= 3) {
    scanAddForm.purchaseContractNumber = parts[0] || "";
    scanAddForm.supplierName = parts[1] || "";
    scanAddForm.projectName = parts[2] || "";
    scanAddForm.contractAmount = parts[3] || "";
    scanAddForm.paymentMethod = parts[4] || "";
  }
};
// å…³é—­æ‰«ç æ–°å¢žå¯¹è¯æ¡†
const closeScanAddDialog = () => {
  scanAddDialogVisible.value = false;
  proxy.resetForm("scanAddFormRef");
};
// æäº¤æ‰«ç æ–°å¢ž
const submitScanAdd = () => {
  proxy.$refs["scanAddFormRef"].validate((valid) => {
    if (valid) {
      // æž„建新增数据
      const newData = {
        purchaseContractNumber: scanAddForm.purchaseContractNumber,
        supplierName: scanAddForm.supplierName,
        projectName: scanAddForm.projectName,
        contractAmount: scanAddForm.contractAmount,
        paymentMethod: scanAddForm.paymentMethod,
        recorderName: scanAddForm.recorderName,
        entryDate: getCurrentDate(),
        remark: scanAddForm.scanRemark,
        type: 2
      };
      // æ¨¡æ‹Ÿæ–°å¢žæˆåŠŸ
      proxy.$modal.msgSuccess("扫码新增成功!");
      closeScanAddDialog();
      // å¯ä»¥é€‰æ‹©æ˜¯å¦åˆ·æ–°åˆ—表
      // getList();
    }
  });
};
// æ‰“开扫码登记对话框
const openScanDialog = (row) => {
  scanForm.purchaseContractNumber = row.purchaseContractNumber;
  scanForm.supplierName = row.supplierName;
  scanForm.projectName = row.projectName;
  scanForm.scanTime = getCurrentDateTime();
  scanForm.scannerName = userStore.nickName;
  scanForm.scanStatus = "未扫码";
  scanForm.scanRemark = "";
  scanRecords.value = [];
  scanDialogVisible.value = true;
};
// å…³é—­æ‰«ç ç™»è®°å¯¹è¯æ¡†
const closeScanDialog = () => {
  scanDialogVisible.value = false;
  proxy.resetForm("scanFormRef");
};
// æäº¤æ‰«ç ç™»è®°
const submitScan = () => {
  proxy.$refs["scanFormRef"].validate((valid) => {
    if (valid) {
      // æ·»åŠ æ‰«ç è®°å½•
      scanRecords.value.push({
        ...scanForm,
        id: Date.now(), // æ¨¡æ‹ŸID
        scanTime: getCurrentDateTime(),
      });
      scanForm.scanStatus = "已扫码";
      scanForm.scanRemark = scanForm.scanRemark || "无";
      proxy.$modal.msgSuccess("扫码登记成功!");
      closeScanDialog();
    }
  });
};
// èŽ·å–å½“å‰æ—¥æœŸæ—¶é—´
function getCurrentDateTime() {
  const now = new Date();
  const year = now.getFullYear();
  const month = String(now.getMonth() + 1).padStart(2, "0");
  const day = String(now.getDate()).padStart(2, "0");
  const hours = String(now.getHours()).padStart(2, "0");
  const minutes = String(now.getMinutes()).padStart(2, "0");
  const seconds = String(now.getSeconds()).padStart(2, "0");
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
onMounted(() => {
  getList();
});