zouyu
8 天以前 648003a577ef7a03046269c77fc5cc64199945e7
Merge branch 'dev_tide' into dev_tide_zlglxt_xinlan

# Conflicts:
# .env.production
# index.html
# package.json
# src/api/collaborativeApproval/attendanceManagement.js
# src/api/collaborativeApproval/enterpriseBook.js
# src/api/inventoryManagement/stockIn.js
# src/api/inventoryManagement/stockManage.js
# src/api/productionManagement/productionOrder.js
# src/router/index.js
# src/store/modules/user.js
# src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
# src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
# src/views/collaborativeApproval/approvalProcess/index.vue
# src/views/collaborativeApproval/enterpriseBook/index.vue
# src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue
# src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue
# src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue
# src/views/collaborativeApproval/notificationManagement/summary/index.vue
# src/views/collaborativeApproval/rulesRegulationsManagement/index.vue
# src/views/collaborativeApproval/sealManagement/index.vue
# src/views/equipmentManagement/upkeep/index.vue
# src/views/financialManagement/accounting/index.vue
# src/views/financialManagement/financialStatements/index.vue
# src/views/financialManagement/inventoryAccounting/index.vue
# src/views/index.vue
# src/views/inventoryManagement/dispatchLog/index.vue
# src/views/inventoryManagement/issueManagement/index.vue
# src/views/inventoryManagement/receiptManagement/components/formDia.vue
# src/views/inventoryManagement/receiptManagement/components/formDiaProduct.vue
# src/views/inventoryManagement/receiptManagement/index.vue
# src/views/inventoryManagement/stockManagement/index.vue
# src/views/inventoryManagement/stockReport/index.vue
# src/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue
# src/views/personnelManagement/onboarding/index.vue
# src/views/procurementManagement/invoiceEntry/indexOld.vue
# src/views/procurementManagement/paymentEntry/index.vue
# src/views/procurementManagement/procurementLedger/index.vue
# src/views/productionManagement/operationScheduling/components/formDia.vue
# src/views/productionManagement/operationScheduling/index.vue
# src/views/productionManagement/productionCosting/index.vue
# src/views/productionManagement/productionDispatching/components/formDia.vue
# src/views/productionManagement/productionDispatching/index.vue
# src/views/productionManagement/productionOrder/index.vue
# src/views/productionManagement/productionReporting/index.vue
# src/views/reportAnalysis/projectProfit/index.vue
# src/views/salesManagement/invoiceRegistration/index.vue
# src/views/salesManagement/receiptPayment/index.vue
# src/views/system/dept/index.vue
# src/views/system/user/index.vue
# src/views/tideLogin.vue
# vite.config.js
已添加15个文件
已修改50个文件
4837 ■■■■■ 文件已修改
.env.production 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/enterpriseBook.js 补丁 | 查看 | 原始文档 | blame | 历史
src/api/financialManagement/financialReconciliation.js 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/financialManagement/fundsManagement.js 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockManage.js 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockWarningLedger.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/procurementLedger.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionOrder.js 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/auth.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/demo/fakePage/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/dynamicEnergySaving/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/gasManagement/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/environmentAccess/accessManagement/index.vue 347 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/environmentAccess/intelligentInspectionManagement/index.vue 581 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/environmentAccess/remoteMonitoringOfEquipment/index.vue 633 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/environmentAccess/vehicleInformationCollection/index.vue 577 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/index.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/financialReconciliation/index.vue 291 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/fundsManagement/index.vue 298 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 244 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/components/formDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/components/formDiaManual.vue 310 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/components/formDiaProduct.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/components/FormDiaManual.vue 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/components/FormDiaProduction.vue 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/components/FormDiaPurchase.vue 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockWarningLedger/index.vue 347 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/job/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/job/log.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/logininfor/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/online/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/operlog/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/onboarding/index.vue 348 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentLedger/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue 100 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementInvoiceLedger/indexOld.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/operationScheduling/components/formDia.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionDispatching/components/formDia.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/safetyMonitoring/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/invoiceLedger/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentHistory/index.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentLedger/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/fileList.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/config/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/data.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/menu/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/notice/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/post/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/authUser.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/selectUser.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/authRole.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tideLogin.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/editTable.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/importTable.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.production
@@ -1,5 +1,5 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = ä¸­å°ä¼ä¸šæ•°å­—化转型二级套餐包
VITE_APP_TITLE = æ–°ç¼†-质量检测管理系统
# ç”Ÿäº§çŽ¯å¢ƒé…ç½®
VITE_APP_ENV = 'production'
src/api/collaborativeApproval/enterpriseBook.js
src/api/financialManagement/financialReconciliation.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢è´¢åŠ¡å¯¹è´¦å°è´¦
export const getReconciliationPage = (params) => {
  return request({
    url: "/finance/reconciliation/page",
    method: "get",
    params,
  });
};
// æ–°å¢žå¯¹è´¦è®°å½•
export const addReconciliation = (data) => {
  return request({
    url: "/finance/reconciliation",
    method: "post",
    data,
  });
};
// æ›´æ–°å¯¹è´¦è®°å½•
export const updateReconciliation = (data) => {
  return request({
    url: "/finance/reconciliation",
    method: "put",
    data,
  });
};
// åˆ é™¤å¯¹è´¦è®°å½•(支持批量)
export const deleteReconciliation = (ids) => {
  return request({
    url: "/finance/reconciliation",
    method: "delete",
    data: ids,
  });
};
src/api/financialManagement/fundsManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢èµ„金管理台账
export const getFundsPage = (params) => {
  return request({
    url: "/finance/funds/page",
    method: "get",
    params,
  });
};
// æ–°å¢žèµ„金记录
export const addFunds = (data) => {
  return request({
    url: "/finance/funds",
    method: "post",
    data,
  });
};
// æ›´æ–°èµ„金记录
export const updateFunds = (data) => {
  return request({
    url: "/finance/funds",
    method: "put",
    data,
  });
};
// åˆ é™¤èµ„金记录(支持批量)
export const deleteFunds = (ids) => {
  return request({
    url: "/finance/funds",
    method: "delete",
    data: ids,
  });
};
src/api/inventoryManagement/stockManage.js
src/api/inventoryManagement/stockWarningLedger.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
import request from "@/utils/request";
// æŸ¥è¯¢åº“存预警台账列表
export const getStockWarningLedgerPage = (params) => {
  return request({
    url: "/customStorageWarning/pageList",
    method: "get",
    params,
  });
};
src/api/procurementManagement/procurementLedger.js
@@ -71,6 +71,33 @@
    url: "/purchase/ledger/createPurchaseNo",
    method: "get",
  });
}
export function updateApprovalStatus(query) {
    return request({
        url: "/purchase/ledger/updateApprovalStatus",
        method: "post",
        data: query,
    });
}
// ä¿å­˜é‡‡è´­æ¨¡æ¿
// /purchase/ledger/addPurchaseTemplate
export function addPurchaseTemplate(data) {
    return request({
        url: "/purchase/ledger/addPurchaseTemplate",
        method: "post",
        data: data,
    });
}
// æŸ¥è¯¢é‡‡è´­æ¨¡æ¿
// /purchase/ledger/getPurchaseTemplateList
export function getPurchaseTemplateList(query) {
    return request({
        url: "/purchase/ledger/getPurchaseTemplateList",
        method: "get",
        params: query,
    });
}
export function updateApprovalStatus(query) {
    return request({
src/api/productionManagement/productionOrder.js
src/components/PIMTable/PIMTable.vue
@@ -15,6 +15,7 @@
    :expand-row-keys="expandRowKeys"
    :show-summary="isShowSummary"
    :summary-method="summaryMethod"
    stripe
    @row-click="rowClick"
    @current-change="currentChange"
    @selection-change="handleSelectionChange"
src/router/index.js
@@ -42,6 +42,11 @@
    component: () => import("@/views/login"),
    hidden: true,
  },
{
    path: "/callbacklccpn",
    component: () => import("@/views/tideLogin.vue"),
    hidden: true,
},
  {
    path: "/register",
    component: () => import("@/views/register"),
src/store/modules/user.js
@@ -115,9 +115,6 @@
              .then((res) => {
                setToken(res.token);
                this.token = res.token
                Vue.prototype.uploadHeader = {
                  Authorization: "Bearer " + res.token,
                };
                resolve();
              })
              .catch((error) => {
src/utils/auth.js
@@ -3,13 +3,16 @@
const TokenKey = 'Admin-Token'
export function getToken() {
  return Cookies.get(TokenKey)
  // return Cookies.get(TokenKey)
    return sessionStorage.getItem(TokenKey)
}
export function setToken(token) {
  return Cookies.set(TokenKey, token)
  // return Cookies.set(TokenKey, token)
    return sessionStorage.setItem(TokenKey, token)
}
export function removeToken() {
  return Cookies.remove(TokenKey)
  // return Cookies.remove(TokenKey)
    return sessionStorage.removeItem(TokenKey)
}
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
@@ -6,7 +6,7 @@
      width="700px"
      @close="closeDia"
    >
            <el-form :model="form" label-width="140px" label-position="top" ref="formRef">
            <el-form :model="form" :rules="rules" label-width="140px" label-position="top" ref="formRef">
                <el-row>
                    <el-col :span="24">
                        <el-form-item label="流程编号:" prop="approveId">
@@ -218,8 +218,12 @@
        approveReason: "",
        checkResult: "",
    },
  rules: {
    // ä½¿ç”¨éƒ¨é—¨ID做必填校验,避免名称未同步导致误报
    approveDeptId: [{ required: true, message: "请选择申请部门", trigger: "change" }],
  },
});
const { form } = toRefs(data);
const { form, rules } = toRefs(data);
// èŠ‚ç‚¹æ ‡é¢˜
const getNodeTitle = (index, len) => {
src/views/demo/fakePage/index.vue
@@ -23,7 +23,7 @@
        <el-button type="success" plain style="float: right" @click="openCreate">新增</el-button>
      </div>
      <el-table :data="pagedList" border style="width: 100%" height="480">
      <el-table :data="pagedList" border style="width: 100%" height="480" stripe>
        <el-table-column prop="id" label="编号" width="90" sortable />
        <el-table-column prop="name" label="名称" min-width="140" />
        <el-table-column prop="category" label="类别" width="120" />
src/views/energyManagement/dynamicEnergySaving/index.vue
@@ -143,7 +143,7 @@
        <span>边缘计算模型配置</span>
      </template>
      
      <el-table :data="modelConfigs" style="width: 100%">
      <el-table :data="modelConfigs" style="width: 100%" stripe>
        <el-table-column prop="modelName" label="模型名称" />
        <el-table-column prop="version" label="版本" />
        <el-table-column prop="status" label="状态">
@@ -193,7 +193,7 @@
    <!-- ä¼˜åŒ–历史对话框 -->
    <el-dialog v-model="historyDialogVisible" title="优化历史记录" width="80%">
      <el-table :data="optimizationHistory" style="width: 100%">
      <el-table :data="optimizationHistory" style="width: 100%" stripe>
        <el-table-column prop="timestamp" label="时间" />
        <el-table-column prop="formationPressure" label="地层压力 (MPa)" />
        <el-table-column prop="oldFrequency" label="原频率 (Hz)" />
src/views/energyManagement/gasManagement/index.vue
@@ -143,7 +143,7 @@
           </div>
        </template>
        
        <el-table :data="deviceList" border style="width: 100%" v-loading="tableLoading">
        <el-table :data="deviceList" border style="width: 100%" v-loading="tableLoading" stripe>
          <el-table-column align="center" label="序号" type="index" width="60" />
          <el-table-column label="设备编号" prop="deviceCode" width="120" show-overflow-tooltip />
          <el-table-column label="设备名称" prop="deviceName" width="150" show-overflow-tooltip />
src/views/environmentAccess/accessManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,347 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="门禁名称">
        <el-input
          v-model="filters.name"
          style="width: 240px"
          placeholder="请输入门禁名称"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="门禁状态">
        <el-select
          v-model="filters.status"
          style="width: 240px"
          placeholder="请选择门禁状态"
          clearable
          @change="getTableData"
        >
          <el-option label="正常" value="1"></el-option>
          <el-option label="异常" value="0"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="区域">
        <el-input
          v-model="filters.area"
          style="width: 240px"
          placeholder="请输入区域"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <div class="actions">
        <div></div>
        <div>
          <el-button type="primary" @click="add" icon="Plus"> æ–°å¢ž </el-button>
          <el-button type="danger" icon="Delete" :disabled="multipleList.length <= 0" @click="batchDelete">批量删除</el-button>
        </div>
      </div>
      <PIMTable
        rowKey="id"
        isSelection
        :column="columns"
        :tableData="dataList"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @selection-change="handleSelectionChange"
        @pagination="changePage"
      >
      </PIMTable>
    </div>
    <!-- æ–°å¢žç¼–辑弹窗 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
      <el-form :model="formData" label-width="100px">
        <el-form-item label="门禁名称" required>
          <el-input v-model="formData.name" placeholder="请输入门禁名称"></el-input>
        </el-form-item>
        <el-form-item label="区域" required>
          <el-input v-model="formData.area" placeholder="请输入区域"></el-input>
        </el-form-item>
        <el-form-item label="位置" required>
          <el-input v-model="formData.location" placeholder="请输入具体位置"></el-input>
        </el-form-item>
        <el-form-item label="状态" required>
          <el-select v-model="formData.status" placeholder="请选择状态">
            <el-option label="正常" value="1"></el-option>
            <el-option label="异常" value="0"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="描述">
          <el-input v-model="formData.description" type="textarea" rows="3" placeholder="请输入描述信息"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="saveData">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue';
import { Search, Plus, Delete } from '@element-plus/icons-vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import dayjs from 'dayjs';
// å®šä¹‰å‡æ•°æ®
const mockData = [
  { id: 1, name: '东门门禁', area: '生产区', location: '东门入口', status: '1', lastUpdate: '2025-12-30 08:30:00', description: '主要用于员工上下班通行' },
  { id: 2, name: '西门门禁', area: '仓储区', location: '西门入口', status: '1', lastUpdate: '2025-12-30 09:15:00', description: '主要用于物流车辆通行' },
  { id: 3, name: '南门门禁', area: '办公区', location: '南门入口', status: '0', lastUpdate: '2025-12-30 10:20:00', description: '主要用于访客通行,当前故障' },
  { id: 4, name: '北门门禁', area: '生产区', location: '北门入口', status: '1', lastUpdate: '2025-12-30 11:45:00', description: '主要用于原材料运输车辆通行' },
  { id: 5, name: '中控室门禁', area: '中控区', location: '中控室门口', status: '1', lastUpdate: '2025-12-30 13:20:00', description: '仅限授权人员进入' },
];
// å“åº”式数据
const filters = reactive({
  name: '',
  status: '',
  area: ''
});
const dataList = ref([]);
const pagination = reactive({
  currentPage: 1,
  pageSize: 10,
  total: 0
});
const multipleList = ref([]);
const dialogVisible = ref(false);
const dialogTitle = ref('新增门禁');
const formData = reactive({
  id: '',
  name: '',
  area: '',
  location: '',
  status: '1',
  description: ''
});
// è¡¨æ ¼åˆ—配置
const columns = [
  {
    label: '门禁名称',
    align: 'center',
    prop: 'name',
  },
  {
    label: '区域',
    align: 'center',
    prop: 'area',
  },
  {
    label: '位置',
    align: 'center',
    prop: 'location',
  },
  {
    label: '状态',
    align: 'center',
    prop: 'status',
    formatter: (row) => {
      return row.status === '1' ? '<el-tag type="success">正常</el-tag>' : '<el-tag type="danger">异常</el-tag>';
    }
  },
  {
    label: '最后更新',
    align: 'center',
    prop: 'lastUpdate',
  },
  {
    label: '描述',
    align: 'center',
    prop: 'description',
  },
  {
    dataType: 'action',
    label: '操作',
    align: 'center',
    fixed: 'right',
    width: 140,
    operation: [
      {
        name: '编辑',
        type: 'text',
        clickFun: (row) => {
          edit(row);
        },
      },
      {
        name: '删除',
        type: 'text',
        clickFun: (row) => {
          deleteRow(row.id);
        },
      },
    ],
  },
];
// è¿‡æ»¤åŽçš„æ•°æ®
const filteredData = computed(() => {
  return mockData.filter(item => {
    const nameMatch = !filters.name || item.name.includes(filters.name);
    const statusMatch = !filters.status || item.status === filters.status;
    const areaMatch = !filters.area || item.area.includes(filters.area);
    return nameMatch && statusMatch && areaMatch;
  });
});
// èŽ·å–è¡¨æ ¼æ•°æ®
const getTableData = () => {
  pagination.total = filteredData.value.length;
  const start = (pagination.currentPage - 1) * pagination.pageSize;
  const end = start + pagination.pageSize;
  dataList.value = filteredData.value.slice(start, end);
};
// é‡ç½®è¿‡æ»¤å™¨
const resetFilters = () => {
  filters.name = '';
  filters.status = '';
  filters.area = '';
  pagination.currentPage = 1;
  getTableData();
};
// åˆ†é¡µå˜åŒ–
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
  pagination.pageSize = limit;
  getTableData();
};
// å¤šé€‰å¤„理
const handleSelectionChange = (selectionList) => {
  multipleList.value = selectionList;
};
// æ–°å¢ž
const add = () => {
  dialogTitle.value = '新增门禁';
  formData.id = '';
  formData.name = '';
  formData.area = '';
  formData.location = '';
  formData.status = '1';
  formData.description = '';
  dialogVisible.value = true;
};
// ç¼–辑
const edit = (row) => {
  dialogTitle.value = '编辑门禁';
  formData.id = row.id;
  formData.name = row.name;
  formData.area = row.area;
  formData.location = row.location;
  formData.status = row.status;
  formData.description = row.description;
  dialogVisible.value = true;
};
// ä¿å­˜æ•°æ®
const saveData = () => {
  if (!formData.name || !formData.area || !formData.location) {
    ElMessage.warning('请填写必填字段');
    return;
  }
  const currentTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
  if (formData.id) {
    // ç¼–辑
    const index = mockData.findIndex(item => item.id === formData.id);
    if (index !== -1) {
      mockData[index] = {
        ...formData,
        lastUpdate: currentTime
      };
      ElMessage.success('编辑成功');
    }
  } else {
    // æ–°å¢ž
    const newId = Math.max(...mockData.map(item => item.id), 0) + 1;
    mockData.unshift({
      ...formData,
      id: newId,
      lastUpdate: currentTime
    });
    ElMessage.success('新增成功');
  }
  dialogVisible.value = false;
  getTableData();
};
// åˆ é™¤
const deleteRow = (id) => {
  ElMessageBox.confirm('此操作将永久删除该门禁记录, æ˜¯å¦ç»§ç»­?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    const index = mockData.findIndex(item => item.id === id);
    if (index !== -1) {
      mockData.splice(index, 1);
      ElMessage.success('删除成功');
      getTableData();
    }
  }).catch(() => {});
};
// æ‰¹é‡åˆ é™¤
const batchDelete = () => {
  if (multipleList.value.length === 0) {
    ElMessage.warning('请选择要删除的数据');
    return;
  }
  ElMessageBox.confirm('此操作将永久删除所选门禁记录, æ˜¯å¦ç»§ç»­?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    const ids = multipleList.value.map(item => item.id);
    mockData.forEach((item, index) => {
      if (ids.includes(item.id)) {
        mockData.splice(index, 1);
      }
    });
    ElMessage.success('删除成功');
    getTableData();
    multipleList.value = [];
  }).catch(() => {});
};
// åˆå§‹åŒ–数据
onMounted(() => {
  getTableData();
});
</script>
<style lang="scss" scoped>
.table_list {
  margin-top: unset;
}
.actions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}
</style>
src/views/environmentAccess/intelligentInspectionManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,581 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="巡检名称">
        <el-input
          v-model="filters.name"
          style="width: 240px"
          placeholder="请输入巡检名称"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="巡检状态">
        <el-select
          v-model="filters.status"
          style="width: 240px"
          placeholder="请选择巡检状态"
          clearable
          @change="getTableData"
        >
          <el-option label="待执行" value="0"></el-option>
          <el-option label="执行中" value="1"></el-option>
          <el-option label="已完成" value="2"></el-option>
          <el-option label="已取消" value="3"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="巡检类型">
        <el-select
          v-model="filters.type"
          style="width: 240px"
          placeholder="请选择巡检类型"
          clearable
          @change="getTableData"
        >
          <el-option label="定期巡检" value="0"></el-option>
          <el-option label="临时巡检" value="1"></el-option>
          <el-option label="故障巡检" value="2"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <div class="actions">
        <div></div>
        <div>
          <el-button type="primary" @click="add" icon="Plus"> æ–°å¢ž </el-button>
          <el-button
            type="danger"
            icon="Delete"
            :disabled="multipleList.length <= 0"
            @click="batchDelete"
            >批量删除</el-button
          >
        </div>
      </div>
      <PIMTable
        rowKey="id"
        isSelection
        :column="columns"
        :tableData="dataList"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @selection-change="handleSelectionChange"
        @pagination="changePage"
      >
      </PIMTable>
    </div>
    <!-- æ–°å¢žç¼–辑弹窗 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
      <el-form :model="formData" label-width="100px">
        <el-form-item label="巡检名称" required>
          <el-input
            v-model="formData.name"
            placeholder="请输入巡检名称"
          ></el-input>
        </el-form-item>
        <el-form-item label="巡检类型" required>
          <el-select v-model="formData.type" placeholder="请选择巡检类型">
            <el-option label="定期巡检" value="0"></el-option>
            <el-option label="临时巡检" value="1"></el-option>
            <el-option label="故障巡检" value="2"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="巡检区域" required>
          <el-input
            v-model="formData.area"
            placeholder="请输入巡检区域"
          ></el-input>
        </el-form-item>
        <el-form-item label="巡检人员" required>
          <el-input
            v-model="formData.inspector"
            placeholder="请输入巡检人员"
          ></el-input>
        </el-form-item>
        <el-form-item label="计划开始时间" required>
          <el-date-picker
            v-model="formData.planStartTime"
            type="datetime"
            placeholder="请选择计划开始时间"
            style="width: 100%"
          ></el-date-picker>
        </el-form-item>
        <el-form-item label="计划结束时间" required>
          <el-date-picker
            v-model="formData.planEndTime"
            type="datetime"
            placeholder="请选择计划结束时间"
            style="width: 100%"
          ></el-date-picker>
        </el-form-item>
        <el-form-item label="巡检内容">
          <el-input
            v-model="formData.content"
            type="textarea"
            rows="3"
            placeholder="请输入巡检内容"
          ></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="saveData">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from "vue";
import { Search, Plus, Delete } from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus";
import dayjs from "dayjs";
// å®šä¹‰å‡æ•°æ®
const mockData = [
  {
    id: 1,
    name: "东门区域日常巡检",
    type: "0",
    status: "2",
    planStartTime: "2025-12-30 08:00:00",
    planEndTime: "2025-12-30 09:00:00",
    actualStartTime: "2025-12-30 08:05:00",
    actualEndTime: "2025-12-30 08:55:00",
    inspector: "张三",
    area: "东门区域",
    content: "检查门禁设备运行状态、环境监测设备数据",
  },
  {
    id: 2,
    name: "西门区域临时巡检",
    type: "1",
    status: "2",
    planStartTime: "2025-12-30 10:00:00",
    planEndTime: "2025-12-30 11:00:00",
    actualStartTime: "2025-12-30 10:00:00",
    actualEndTime: "2025-12-30 10:45:00",
    inspector: "李四",
    area: "西门区域",
    content: "检查异常报警设备",
  },
  {
    id: 3,
    name: "南门门禁故障巡检",
    type: "2",
    status: "1",
    planStartTime: "2025-12-30 13:00:00",
    planEndTime: "2025-12-30 14:00:00",
    actualStartTime: "2025-12-30 13:10:00",
    actualEndTime: "",
    inspector: "王五",
    area: "南门区域",
    content: "修复门禁故障",
  },
  {
    id: 4,
    name: "中控室定期巡检",
    type: "0",
    status: "0",
    planStartTime: "2025-12-31 09:00:00",
    planEndTime: "2025-12-31 10:00:00",
    actualStartTime: "",
    actualEndTime: "",
    inspector: "赵六",
    area: "中控区域",
    content: "检查监控设备、服务器运行状态",
  },
  {
    id: 5,
    name: "北门区域日常巡检",
    type: "0",
    status: "2",
    planStartTime: "2025-12-30 15:00:00",
    planEndTime: "2025-12-30 16:00:00",
    actualStartTime: "2025-12-30 15:00:00",
    actualEndTime: "2025-12-30 15:50:00",
    inspector: "张三",
    area: "北门区域",
    content: "检查车辆识别设备、道闸运行状态",
  },
];
// å“åº”式数据
const filters = reactive({
  name: "",
  status: "",
  type: "",
});
const dataList = ref([]);
const pagination = reactive({
  currentPage: 1,
  pageSize: 10,
  total: 0,
});
const multipleList = ref([]);
const dialogVisible = ref(false);
const dialogTitle = ref("新增巡检");
const formData = reactive({
  id: "",
  name: "",
  type: "0",
  status: "0",
  planStartTime: "",
  planEndTime: "",
  actualStartTime: "",
  actualEndTime: "",
  inspector: "",
  area: "",
  content: "",
});
// çŠ¶æ€æ˜ å°„
const statusMap = {
  '0': '<el-tag type="warning">待执行</el-tag>',
  '1': '<el-tag type="primary">执行中</el-tag>',
  '2': '<el-tag type="success">已完成</el-tag>',
  '3': '<el-tag type="danger">已取消</el-tag>',
};
// ç±»åž‹æ˜ å°„
const typeMap = {
  0: "定期巡检",
  1: "临时巡检",
  2: "故障巡检",
};
// è¡¨æ ¼åˆ—配置
const columns = [
  {
    label: "巡检名称",
    align: "center",
    prop: "name",
  },
  {
    label: "巡检类型",
    align: "center",
    prop: "type",
    formatter: (row) => {
      return typeMap[row.type];
    },
  },
  {
    label: "巡检状态",
    align: "center",
    prop: "status",
    formatter: (row) => {
      return statusMap[row.status];
    },
  },
  {
    label: "计划开始时间",
    align: "center",
    prop: "planStartTime",
  },
  {
    label: "计划结束时间",
    align: "center",
    prop: "planEndTime",
  },
  {
    label: "实际开始时间",
    align: "center",
    prop: "actualStartTime",
    formatter: (row) => {
      return row.actualStartTime || "-";
    },
  },
  {
    label: "实际结束时间",
    align: "center",
    prop: "actualEndTime",
    formatter: (row) => {
      return row.actualEndTime || "-";
    },
  },
  {
    label: "巡检人员",
    align: "center",
    prop: "inspector",
  },
  {
    label: "巡检区域",
    align: "center",
    prop: "area",
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 180,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          edit(row);
        },
      },
      {
        name: "开始执行",
        type: "text",
        clickFun: (row) => {
          startInspection(row);
        },
        visible: (row) => {
          return row.status === "0";
        },
      },
      {
        name: "完成巡检",
        type: "text",
        clickFun: (row) => {
          completeInspection(row);
        },
        visible: (row) => {
          return row.status === "1";
        },
      },
      {
        name: "取消巡检",
        type: "text",
        clickFun: (row) => {
          cancelInspection(row);
        },
        visible: (row) => {
          return ["0", "1"].includes(row.status);
        },
      },
      {
        name: "删除",
        type: "text",
        clickFun: (row) => {
          deleteRow(row.id);
        },
      },
    ],
  },
];
// è¿‡æ»¤åŽçš„æ•°æ®
const filteredData = computed(() => {
  return mockData.filter((item) => {
    const nameMatch = !filters.name || item.name.includes(filters.name);
    const statusMatch = !filters.status || item.status === filters.status;
    const typeMatch = !filters.type || item.type === filters.type;
    return nameMatch && statusMatch && typeMatch;
  });
});
// èŽ·å–è¡¨æ ¼æ•°æ®
const getTableData = () => {
  pagination.total = filteredData.value.length;
  const start = (pagination.currentPage - 1) * pagination.pageSize;
  const end = start + pagination.pageSize;
  dataList.value = filteredData.value.slice(start, end);
};
// é‡ç½®è¿‡æ»¤å™¨
const resetFilters = () => {
  filters.name = "";
  filters.status = "";
  filters.type = "";
  pagination.currentPage = 1;
  getTableData();
};
// åˆ†é¡µå˜åŒ–
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
  pagination.pageSize = limit;
  getTableData();
};
// å¤šé€‰å¤„理
const handleSelectionChange = (selectionList) => {
  multipleList.value = selectionList;
};
// æ–°å¢ž
const add = () => {
  dialogTitle.value = "新增巡检";
  formData.id = "";
  formData.name = "";
  formData.type = "0";
  formData.status = "0";
  formData.planStartTime = "";
  formData.planEndTime = "";
  formData.actualStartTime = "";
  formData.actualEndTime = "";
  formData.inspector = "";
  formData.area = "";
  formData.content = "";
  dialogVisible.value = true;
};
// ç¼–辑
const edit = (row) => {
  dialogTitle.value = "编辑巡检";
  formData.id = row.id;
  formData.name = row.name;
  formData.type = row.type;
  formData.status = row.status;
  formData.planStartTime = row.planStartTime;
  formData.planEndTime = row.planEndTime;
  formData.actualStartTime = row.actualStartTime;
  formData.actualEndTime = row.actualEndTime;
  formData.inspector = row.inspector;
  formData.area = row.area;
  formData.content = row.content;
  dialogVisible.value = true;
};
// ä¿å­˜æ•°æ®
const saveData = () => {
  if (
    !formData.name ||
    !formData.planStartTime ||
    !formData.planEndTime ||
    !formData.inspector ||
    !formData.area
  ) {
    ElMessage.warning("请填写必填字段");
    return;
  }
  if (formData.id) {
    // ç¼–辑
    const index = mockData.findIndex((item) => item.id === formData.id);
    if (index !== -1) {
      mockData[index] = {
        ...mockData[index],
        ...formData,
      };
      ElMessage.success("编辑成功");
    }
  } else {
    // æ–°å¢ž
    const newId = Math.max(...mockData.map((item) => item.id), 0) + 1;
    mockData.unshift({
      ...formData,
      id: newId,
    });
    ElMessage.success("新增成功");
  }
  dialogVisible.value = false;
  getTableData();
};
// å¼€å§‹å·¡æ£€
const startInspection = (row) => {
  const index = mockData.findIndex((item) => item.id === row.id);
  if (index !== -1) {
    mockData[index].status = "1";
    mockData[index].actualStartTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
    ElMessage.success("巡检已开始");
    getTableData();
  }
};
// å®Œæˆå·¡æ£€
const completeInspection = (row) => {
  const index = mockData.findIndex((item) => item.id === row.id);
  if (index !== -1) {
    mockData[index].status = "2";
    mockData[index].actualEndTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
    ElMessage.success("巡检已完成");
    getTableData();
  }
};
// å–消巡检
const cancelInspection = (row) => {
  ElMessageBox.confirm("确定要取消该巡检吗?", "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      const index = mockData.findIndex((item) => item.id === row.id);
      if (index !== -1) {
        mockData[index].status = "3";
        ElMessage.success("巡检已取消");
        getTableData();
      }
    })
    .catch(() => {});
};
// åˆ é™¤
const deleteRow = (id) => {
  ElMessageBox.confirm("此操作将永久删除该巡检记录, æ˜¯å¦ç»§ç»­?", "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      const index = mockData.findIndex((item) => item.id === id);
      if (index !== -1) {
        mockData.splice(index, 1);
        ElMessage.success("删除成功");
        getTableData();
      }
    })
    .catch(() => {});
};
// æ‰¹é‡åˆ é™¤
const batchDelete = () => {
  if (multipleList.value.length === 0) {
    ElMessage.warning("请选择要删除的数据");
    return;
  }
  ElMessageBox.confirm("此操作将永久删除所选巡检记录, æ˜¯å¦ç»§ç»­?", "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      const ids = multipleList.value.map((item) => item.id);
      mockData.forEach((item, index) => {
        if (ids.includes(item.id)) {
          mockData.splice(index, 1);
        }
      });
      ElMessage.success("删除成功");
      getTableData();
      multipleList.value = [];
    })
    .catch(() => {});
};
// åˆå§‹åŒ–数据
onMounted(() => {
  getTableData();
});
</script>
<style lang="scss" scoped>
.table_list {
  margin-top: unset;
}
.actions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}
</style>
src/views/environmentAccess/remoteMonitoringOfEquipment/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,633 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="设备名称">
        <el-input
          v-model="filters.name"
          style="width: 240px"
          placeholder="请输入设备名称"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="设备状态">
        <el-select
          v-model="filters.status"
          style="width: 240px"
          placeholder="请选择设备状态"
          clearable
          @change="getTableData"
        >
          <el-option label="在线" value="1"></el-option>
          <el-option label="离线" value="0"></el-option>
          <el-option label="故障" value="2"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="设备类型">
        <el-select
          v-model="filters.type"
          style="width: 240px"
          placeholder="请选择设备类型"
          clearable
          @change="getTableData"
        >
          <el-option label="门禁设备" value="0"></el-option>
          <el-option label="环境监测设备" value="1"></el-option>
          <el-option label="视频监控设备" value="2"></el-option>
          <el-option label="智能道闸" value="3"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="monitor-container">
      <div class="monitor-stats">
        <div class="stat-card">
          <div class="stat-title">在线设备</div>
          <div class="stat-value online">{{ onlineCount }}</div>
        </div>
        <div class="stat-card">
          <div class="stat-title">离线设备</div>
          <div class="stat-value offline">{{ offlineCount }}</div>
        </div>
        <div class="stat-card">
          <div class="stat-title">故障设备</div>
          <div class="stat-value error">{{ errorCount }}</div>
        </div>
        <div class="stat-card">
          <div class="stat-title">设备总数</div>
          <div class="stat-value total">{{ totalCount }}</div>
        </div>
      </div>
      <div class="table_list">
        <PIMTable
          rowKey="id"
          :column="columns"
          :tableData="dataList"
          :page="{
            current: pagination.currentPage,
            size: pagination.pageSize,
            total: pagination.total,
          }"
          @pagination="changePage"
        >
        </PIMTable>
      </div>
    </div>
    <!-- è®¾å¤‡è¯¦æƒ…弹窗 -->
    <el-dialog
      v-model="detailVisible"
      :title="`设备详情 - ${selectedDevice.name}`"
      width="800px"
    >
      <div v-if="selectedDevice" class="device-detail">
        <div class="detail-section">
          <h3>基本信息</h3>
          <el-descriptions :column="2" border>
            <el-descriptions-item label="设备名称">{{
              selectedDevice.name
            }}</el-descriptions-item>
            <el-descriptions-item label="设备类型">{{
              typeMap[selectedDevice.type]
            }}</el-descriptions-item>
            <el-descriptions-item label="设备状态">{{
              statusMap[selectedDevice.status]
            }}</el-descriptions-item>
            <el-descriptions-item label="设备编号">{{
              selectedDevice.code
            }}</el-descriptions-item>
            <el-descriptions-item label="安装位置">{{
              selectedDevice.location
            }}</el-descriptions-item>
            <el-descriptions-item label="安装时间">{{
              selectedDevice.installTime
            }}</el-descriptions-item>
            <el-descriptions-item label="最后维护时间">{{
              selectedDevice.maintainTime
            }}</el-descriptions-item>
            <el-descriptions-item label="责任人">{{
              selectedDevice.responsiblePerson
            }}</el-descriptions-item>
          </el-descriptions>
        </div>
        <div class="detail-section">
          <h3>实时数据</h3>
          <div class="realtime-data">
            <div
              v-for="data in selectedDevice.realtimeData"
              :key="data.name"
              class="data-item"
            >
              <div class="data-name">{{ data.name }}</div>
              <div class="data-value">{{ data.value }}{{ data.unit }}</div>
              <div class="data-status" :class="data.status">
                {{ data.status === "normal" ? "正常" : "异常" }}
              </div>
            </div>
          </div>
        </div>
        <div class="detail-section">
          <h3>控制操作</h3>
          <div class="control-buttons">
            <el-button
              type="primary"
              @click="controlDevice('restart')"
              :disabled="selectedDevice.status !== '1'"
              >重启设备</el-button
            >
            <el-button
              type="warning"
              @click="controlDevice('reset')"
              :disabled="selectedDevice.status !== '1'"
              >重置设备</el-button
            >
            <el-button
              type="danger"
              @click="controlDevice('shutdown')"
              :disabled="selectedDevice.status !== '1'"
              >关闭设备</el-button
            >
            <el-button
              type="success"
              @click="controlDevice('update')"
              :disabled="selectedDevice.status !== '1'"
              >固件更新</el-button
            >
          </div>
        </div>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="detailVisible = false">关闭</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from "vue";
import {
  Search
} from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus";
// å®šä¹‰å‡æ•°æ®
const mockDevices = [
  {
    id: 1,
    name: "东门门禁控制器",
    code: "DEV-20250001",
    type: "0",
    status: "1",
    location: "东门入口",
    installTime: "2025-01-15",
    maintainTime: "2025-12-20",
    responsiblePerson: "张三",
    realtimeData: [
      { name: "温度", value: 23.5, unit: "℃", status: "normal" },
      { name: "湿度", value: 45, unit: "%", status: "normal" },
      { name: "运行时长", value: 12560, unit: "h", status: "normal" },
      { name: "门禁开关次数", value: 234, unit: "次/天", status: "normal" },
    ],
  },
  {
    id: 2,
    name: "西门视频监控",
    code: "DEV-20250002",
    type: "2",
    status: "1",
    location: "西门出口",
    installTime: "2025-02-20",
    maintainTime: "2025-11-15",
    responsiblePerson: "李四",
    realtimeData: [
      { name: "视频清晰度", value: 1080, unit: "P", status: "normal" },
      { name: "帧率", value: 25, unit: "fps", status: "normal" },
      { name: "存储空间", value: 78, unit: "%", status: "warning" },
      { name: "网络延迟", value: 12, unit: "ms", status: "normal" },
    ],
  },
  {
    id: 3,
    name: "南门环境监测仪",
    code: "DEV-20250003",
    type: "1",
    status: "2",
    location: "南门广场",
    installTime: "2025-03-10",
    maintainTime: "2025-10-05",
    responsiblePerson: "王五",
    realtimeData: [
      { name: "PM2.5", value: 85, unit: "μg/m³", status: "error" },
      { name: "PM10", value: 120, unit: "μg/m³", status: "error" },
      { name: "噪声", value: 65, unit: "dB", status: "normal" },
      { name: "风速", value: 1.2, unit: "m/s", status: "normal" },
    ],
  },
  {
    id: 4,
    name: "北门智能道闸",
    code: "DEV-20250004",
    type: "3",
    status: "0",
    location: "北门入口",
    installTime: "2025-04-05",
    maintainTime: "2025-09-20",
    responsiblePerson: "赵六",
    realtimeData: [
      { name: "开关次数", value: 156, unit: "次/天", status: "normal" },
      { name: "运行电压", value: 220, unit: "V", status: "normal" },
      { name: "运行电流", value: 5.2, unit: "A", status: "normal" },
      { name: "故障代码", value: "E001", unit: "", status: "error" },
    ],
  },
  {
    id: 5,
    name: "中控室服务器",
    code: "DEV-20250005",
    type: "1",
    status: "1",
    location: "中控室",
    installTime: "2025-05-15",
    maintainTime: "2025-12-01",
    responsiblePerson: "孙七",
    realtimeData: [
      { name: "CPU使用率", value: 45, unit: "%", status: "normal" },
      { name: "内存使用率", value: 68, unit: "%", status: "warning" },
      { name: "磁盘使用率", value: 82, unit: "%", status: "warning" },
      { name: "网络流量", value: 125, unit: "Mbps", status: "normal" },
    ],
  },
];
// å“åº”式数据
const filters = reactive({
  name: "",
  status: "",
  type: "",
});
const dataList = ref([]);
const pagination = reactive({
  currentPage: 1,
  pageSize: 10,
  total: 0,
});
const detailVisible = ref(false);
const selectedDevice = ref({});
// çŠ¶æ€æ˜ å°„
const statusMap = {
  0: '<el-tag type="warning">离线</el-tag>',
  1: '<el-tag type="success">在线</el-tag>',
  2: '<el-tag type="danger">故障</el-tag>',
};
// ç±»åž‹æ˜ å°„
const typeMap = {
  0: "门禁设备",
  1: "环境监测设备",
  2: "视频监控设备",
  3: "智能道闸",
};
// è®¾å¤‡æ•°é‡ç»Ÿè®¡
const onlineCount = computed(() => {
  return mockDevices.filter((device) => device.status === "1").length;
});
const offlineCount = computed(() => {
  return mockDevices.filter((device) => device.status === "0").length;
});
const errorCount = computed(() => {
  return mockDevices.filter((device) => device.status === "2").length;
});
const totalCount = computed(() => {
  return mockDevices.length;
});
// è¡¨æ ¼åˆ—配置
const columns = [
  {
    label: "设备名称",
    align: "center",
    prop: "name",
  },
  {
    label: "设备编号",
    align: "center",
    prop: "code",
  },
  {
    label: "设备类型",
    align: "center",
    prop: "type",
    formatter: (row) => {
      return typeMap[row.type];
    },
  },
  {
    label: "设备状态",
    align: "center",
    prop: "status",
    formatter: (row) => {
      return statusMap[row.status];
    },
  },
  {
    label: "安装位置",
    align: "center",
    prop: "location",
  },
  {
    label: "责任人",
    align: "center",
    prop: "responsiblePerson",
  },
  {
    label: "最后维护时间",
    align: "center",
    prop: "maintainTime",
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 140,
    operation: [
      {
        name: "查看详情",
        type: "text",
        clickFun: (row) => {
          viewDetail(row);
        },
      },
      {
        name: "实时监控",
        type: "text",
        clickFun: (row) => {
          realtimeMonitor(row);
        },
        visible: (row) => {
          return row.status === "1";
        },
      },
      {
        name: "重启设备",
        type: "text",
        clickFun: (row) => {
          controlDevice("restart", row);
        },
        visible: (row) => {
          return row.status === "1";
        },
      },
    ],
  },
];
// è¿‡æ»¤åŽçš„æ•°æ®
const filteredData = computed(() => {
  return mockDevices.filter((item) => {
    const nameMatch = !filters.name || item.name.includes(filters.name);
    const statusMatch = !filters.status || item.status === filters.status;
    const typeMatch = !filters.type || item.type === filters.type;
    return nameMatch && statusMatch && typeMatch;
  });
});
// èŽ·å–è¡¨æ ¼æ•°æ®
const getTableData = () => {
  pagination.total = filteredData.value.length;
  const start = (pagination.currentPage - 1) * pagination.pageSize;
  const end = start + pagination.pageSize;
  dataList.value = filteredData.value.slice(start, end);
};
// é‡ç½®è¿‡æ»¤å™¨
const resetFilters = () => {
  filters.name = "";
  filters.status = "";
  filters.type = "";
  pagination.currentPage = 1;
  getTableData();
};
// åˆ†é¡µå˜åŒ–
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
  pagination.pageSize = limit;
  getTableData();
};
// æŸ¥çœ‹è®¾å¤‡è¯¦æƒ…
const viewDetail = (row) => {
  selectedDevice.value = row;
  detailVisible.value = true;
};
// å®žæ—¶ç›‘控
const realtimeMonitor = (row) => {
  ElMessage.info(`正在跳转到${row.name}的实时监控页面...`);
  // è¿™é‡Œå¯ä»¥è·³è½¬åˆ°å®žæ—¶ç›‘控页面或打开监控窗口
};
// è®¾å¤‡æŽ§åˆ¶
const controlDevice = (action, row = selectedDevice.value) => {
  const actionMap = {
    restart: "重启",
    reset: "重置",
    shutdown: "关闭",
    update: "固件更新",
  };
  ElMessageBox.confirm(
    `确定要对设备 ${row.name} æ‰§è¡Œ${actionMap[action]}操作吗?`,
    "提示",
    {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    }
  )
    .then(() => {
      ElMessage.success(`设备${actionMap[action]}操作已发送`);
      // æ¨¡æ‹Ÿè®¾å¤‡å“åº”
      setTimeout(() => {
        ElMessage.success(`设备${actionMap[action]}操作执行成功`);
      }, 1000);
    })
    .catch(() => {});
};
// å®šæ—¶æ›´æ–°è®¾å¤‡çŠ¶æ€ï¼ˆæ¨¡æ‹Ÿå®žæ—¶å˜åŒ–ï¼‰
const updateDeviceStatus = () => {
  // éšæœºæ›´æ–°ä¸€äº›è®¾å¤‡çš„状态和实时数据
  mockDevices.forEach((device) => {
    if (Math.random() > 0.8) {
      // 80%概率不更新
      return;
    }
    // éšæœºæ›´æ–°çŠ¶æ€
    const statuses = ["0", "1", "2"];
    device.status = statuses[Math.floor(Math.random() * statuses.length)];
    // æ›´æ–°å®žæ—¶æ•°æ®
    device.realtimeData.forEach((data) => {
      if (data.name === "温度") {
        data.value = (20 + Math.random() * 10).toFixed(1);
        data.status = data.value > 30 ? "error" : "normal";
      } else if (data.name === "湿度") {
        data.value = Math.floor(30 + Math.random() * 50);
        data.status = data.value > 80 ? "error" : "normal";
      } else if (data.name === "CPU使用率") {
        data.value = Math.floor(20 + Math.random() * 60);
        data.status =
          data.value > 80 ? "error" : data.value > 60 ? "warning" : "normal";
      }
    });
  });
  // åˆ·æ–°è¡¨æ ¼æ•°æ®
  getTableData();
};
// åˆå§‹åŒ–数据
onMounted(() => {
  getTableData();
  // æ¯5秒更新一次设备状态
  const interval = setInterval(updateDeviceStatus, 5000);
  // æ¸…理定时器
  return () => clearInterval(interval);
});
</script>
<style lang="scss" scoped>
.table_list {
  margin-top: unset;
}
.monitor-container {
  .monitor-stats {
    display: flex;
    gap: 20px;
    margin-bottom: 20px;
    .stat-card {
      flex: 1;
      background: #fff;
      padding: 20px;
      border-radius: 8px;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      text-align: center;
      .stat-title {
        font-size: 14px;
        color: #606266;
        margin-bottom: 10px;
      }
      .stat-value {
        font-size: 32px;
        font-weight: bold;
        &.online {
          color: #67c23a;
        }
        &.offline {
          color: #e6a23c;
        }
        &.error {
          color: #f56c6c;
        }
        &.total {
          color: #409eff;
        }
      }
    }
  }
}
.device-detail {
  .detail-section {
    margin-bottom: 20px;
    h3 {
      font-size: 16px;
      font-weight: bold;
      margin-bottom: 10px;
      color: #303133;
    }
  }
  .realtime-data {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 15px;
    .data-item {
      background: #f5f7fa;
      padding: 15px;
      border-radius: 8px;
      .data-name {
        font-size: 14px;
        color: #606266;
        margin-bottom: 5px;
      }
      .data-value {
        font-size: 24px;
        font-weight: bold;
        color: #303133;
        margin-bottom: 5px;
      }
      .data-status {
        font-size: 12px;
        padding: 2px 8px;
        border-radius: 10px;
        &.normal {
          background: #f0f9eb;
          color: #67c23a;
        }
        &.warning {
          background: #fdf6ec;
          color: #e6a23c;
        }
        &.error {
          background: #fef0f0;
          color: #f56c6c;
        }
      }
    }
  }
  .control-buttons {
    display: flex;
    gap: 10px;
    margin-top: 10px;
  }
}
</style>
src/views/environmentAccess/vehicleInformationCollection/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,577 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="车牌号码">
        <el-input
          v-model="filters.plateNumber"
          style="width: 240px"
          placeholder="请输入车牌号码"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="车辆类型">
        <el-select
          v-model="filters.vehicleType"
          style="width: 240px"
          placeholder="请选择车辆类型"
          clearable
          @change="getTableData"
        >
          <el-option label="小型车" value="0"></el-option>
          <el-option label="中型车" value="1"></el-option>
          <el-option label="大型车" value="2"></el-option>
          <el-option label="特种车" value="3"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="进出方向">
        <el-select
          v-model="filters.direction"
          style="width: 240px"
          placeholder="请选择进出方向"
          clearable
          @change="getTableData"
        >
          <el-option label="进" value="0"></el-option>
          <el-option label="出" value="1"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="进出时间">
        <el-date-picker
          v-model="filters.timeRange"
          type="daterange"
          start-placeholder="开始时间"
          end-placeholder="结束时间"
          value-format="YYYY-MM-DD HH:mm:ss"
          format="YYYY-MM-DD HH:mm"
          clearable
          @change="changeTimeRange"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="vehicle-stats">
      <div class="stat-card">
        <div class="stat-title">今日进车数</div>
        <div class="stat-value in">{{ todayInCount }}</div>
      </div>
      <div class="stat-card">
        <div class="stat-title">今日出车数</div>
        <div class="stat-value out">{{ todayOutCount }}</div>
      </div>
      <div class="stat-card">
        <div class="stat-title">当前在场车辆</div>
        <div class="stat-value present">{{ presentCount }}</div>
      </div>
      <div class="stat-card">
        <div class="stat-title">今日总流量</div>
        <div class="stat-value total">{{ todayTotalCount }}</div>
      </div>
    </div>
    <div class="table_list">
      <PIMTable
        rowKey="id"
        :column="columns"
        :tableData="dataList"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @pagination="changePage"
      >
      </PIMTable>
    </div>
    <!-- è½¦è¾†è¯¦æƒ…弹窗 -->
    <el-dialog
      v-model="detailVisible"
      :title="`车辆详情 - ${selectedVehicle.plateNumber}`"
      width="700px"
    >
      <div v-if="selectedVehicle" class="vehicle-detail">
        <div class="detail-section">
          <h3>基本信息</h3>
          <el-descriptions :column="2" border>
            <el-descriptions-item label="车牌号码">{{
              selectedVehicle.plateNumber
            }}</el-descriptions-item>
            <el-descriptions-item label="车辆类型">{{
              typeMap[selectedVehicle.vehicleType]
            }}</el-descriptions-item>
            <el-descriptions-item label="车辆颜色">{{
              selectedVehicle.color
            }}</el-descriptions-item>
            <el-descriptions-item label="进出方向">{{
              selectedVehicle.direction === "0" ? "进" : "出"
            }}</el-descriptions-item>
            <el-descriptions-item label="进出时间">{{
              selectedVehicle.timestamp
            }}</el-descriptions-item>
            <el-descriptions-item label="通道名称">{{
              selectedVehicle.channelName
            }}</el-descriptions-item>
            <el-descriptions-item label="识别方式">{{
              selectedVehicle.identifyMethod
            }}</el-descriptions-item>
            <el-descriptions-item label="车辆状态">{{
              statusMap[selectedVehicle.status]
            }}</el-descriptions-item>
          </el-descriptions>
        </div>
        <div class="detail-section">
          <h3>车辆图片</h3>
          <div class="vehicle-images">
            <div class="image-item">
              <div class="image-title">正面图片</div>
              <el-image
                :src="selectedVehicle.frontImage"
                fit="cover"
                style="width: 100%; height: 200px"
                :preview-src-list="[
                  selectedVehicle.frontImage,
                  selectedVehicle.sideImage,
                ]"
              ></el-image>
            </div>
            <div class="image-item">
              <div class="image-title">侧面图片</div>
              <el-image
                :src="selectedVehicle.sideImage"
                fit="cover"
                style="width: 100%; height: 200px"
                :preview-src-list="[
                  selectedVehicle.sideImage,
                  selectedVehicle.frontImage,
                ]"
              ></el-image>
            </div>
          </div>
        </div>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="detailVisible = false">关闭</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from "vue";
import { Search } from "@element-plus/icons-vue";
import dayjs from "dayjs";
// å®šä¹‰å‡æ•°æ®
const mockVehicles = [
  {
    id: 1,
    plateNumber: "京A12345",
    vehicleType: "0",
    color: "白色",
    direction: "0",
    timestamp: "2025-12-31 08:15:30",
    channelName: "东门通道",
    identifyMethod: "车牌识别",
    status: "0",
    frontImage: "https://picsum.photos/id/1024/800/600",
    sideImage: "https://picsum.photos/id/1025/800/600",
    duration: "00:00:00",
    present: true,
  },
  {
    id: 2,
    plateNumber: "沪B67890",
    vehicleType: "1",
    color: "黑色",
    direction: "1",
    timestamp: "2025-12-31 08:10:20",
    channelName: "西门通道",
    identifyMethod: "车牌识别",
    status: "1",
    frontImage: "https://picsum.photos/id/1026/800/600",
    sideImage: "https://picsum.photos/id/1027/800/600",
    duration: "01:25:30",
    present: false,
  },
  {
    id: 3,
    plateNumber: "粤C54321",
    vehicleType: "0",
    color: "红色",
    direction: "0",
    timestamp: "2025-12-31 08:05:15",
    channelName: "南门通道",
    identifyMethod: "IC卡识别",
    status: "0",
    frontImage: "https://picsum.photos/id/1028/800/600",
    sideImage: "https://picsum.photos/id/1029/800/600",
    duration: "00:05:45",
    present: true,
  },
  {
    id: 4,
    plateNumber: "川D98765",
    vehicleType: "2",
    color: "蓝色",
    direction: "0",
    timestamp: "2025-12-31 08:00:00",
    channelName: "北门通道",
    identifyMethod: "车牌识别",
    status: "0",
    frontImage: "https://picsum.photos/id/1030/800/600",
    sideImage: "https://picsum.photos/id/1031/800/600",
    duration: "00:10:30",
    present: true,
  },
  {
    id: 5,
    plateNumber: "苏E13579",
    vehicleType: "0",
    color: "银色",
    direction: "1",
    timestamp: "2025-12-31 07:55:45",
    channelName: "东门通道",
    identifyMethod: "车牌识别",
    status: "1",
    frontImage: "https://picsum.photos/id/1032/800/600",
    sideImage: "https://picsum.photos/id/1033/800/600",
    duration: "02:30:15",
    present: false,
  },
  {
    id: 6,
    plateNumber: "浙F24680",
    vehicleType: "3",
    color: "黄色",
    direction: "0",
    timestamp: "2025-12-31 07:50:30",
    channelName: "南门通道",
    identifyMethod: "人工登记",
    status: "0",
    frontImage: "https://picsum.photos/id/1034/800/600",
    sideImage: "https://picsum.photos/id/1035/800/600",
    duration: "00:15:30",
    present: true,
  },
  {
    id: 7,
    plateNumber: "鲁G97531",
    vehicleType: "1",
    color: "绿色",
    direction: "1",
    timestamp: "2025-12-31 07:45:15",
    channelName: "西门通道",
    identifyMethod: "车牌识别",
    status: "1",
    frontImage: "https://picsum.photos/id/1036/800/600",
    sideImage: "https://picsum.photos/id/1037/800/600",
    duration: "01:45:30",
    present: false,
  },
  {
    id: 8,
    plateNumber: "豫H86420",
    vehicleType: "0",
    color: "灰色",
    direction: "0",
    timestamp: "2025-12-31 07:40:00",
    channelName: "北门通道",
    identifyMethod: "IC卡识别",
    status: "0",
    frontImage: "https://picsum.photos/id/1038/800/600",
    sideImage: "https://picsum.photos/id/1039/800/600",
    duration: "00:20:15",
    present: true,
  },
];
// å“åº”式数据
const filters = reactive({
  plateNumber: "",
  vehicleType: "",
  direction: "",
  timeRange: [],
});
const dataList = ref([]);
const pagination = reactive({
  currentPage: 1,
  pageSize: 10,
  total: 0,
});
const detailVisible = ref(false);
const selectedVehicle = ref({});
// æ—¶é—´èŒƒå›´
const timeRange = reactive({
  startTime: "",
  endTime: "",
});
// çŠ¶æ€æ˜ å°„
const statusMap = {
  '0': '<el-tag type="success">正常</el-tag>',
  '1': '<el-tag type="info">已离开</el-tag>',
  '2': '<el-tag type="warning">异常</el-tag>',
};
// ç±»åž‹æ˜ å°„
const typeMap = {
  0: "小型车",
  1: "中型车",
  2: "大型车",
  3: "特种车",
};
// ç»Ÿè®¡æ•°æ®
const todayInCount = computed(() => {
  const today = dayjs().format("YYYY-MM-DD");
  return mockVehicles.filter((vehicle) => {
    return vehicle.direction === "0" && vehicle.timestamp.startsWith(today);
  }).length;
});
const todayOutCount = computed(() => {
  const today = dayjs().format("YYYY-MM-DD");
  return mockVehicles.filter((vehicle) => {
    return vehicle.direction === "1" && vehicle.timestamp.startsWith(today);
  }).length;
});
const presentCount = computed(() => {
  return mockVehicles.filter((vehicle) => vehicle.present).length;
});
const todayTotalCount = computed(() => {
  const today = dayjs().format("YYYY-MM-DD");
  return mockVehicles.filter((vehicle) => {
    return vehicle.timestamp.startsWith(today);
  }).length;
});
// è¡¨æ ¼åˆ—配置
const columns = [
  {
    label: "车牌号码",
    align: "center",
    prop: "plateNumber",
  },
  {
    label: "车辆类型",
    align: "center",
    prop: "vehicleType",
    formatter: (row) => {
      return typeMap[row.vehicleType];
    },
  },
  {
    label: "车辆颜色",
    align: "center",
    prop: "color",
  },
  {
    label: "进出方向",
    align: "center",
    prop: "direction",
    formatter: (row) => {
      return row.direction === "0" ? '<el-tag type="success">进</el-tag>' : '<el-tag type="info">出</el-tag>';
    },
  },
  {
    label: "进出时间",
    align: "center",
    prop: "timestamp",
  },
  {
    label: "通道名称",
    align: "center",
    prop: "channelName",
  },
  {
    label: "识别方式",
    align: "center",
    prop: "identifyMethod",
  },
  {
    label: "车辆状态",
    align: "center",
    prop: "status",
    formatter: (row) => {
      return statusMap[row.status];
    },
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 140,
    operation: [
      {
        name: "查看详情",
        type: "text",
        clickFun: (row) => {
          viewDetail(row);
        },
      },
    ],
  },
];
// è¿‡æ»¤åŽçš„æ•°æ®
const filteredData = computed(() => {
  return mockVehicles.filter((item) => {
    const plateMatch =
      !filters.plateNumber || item.plateNumber.includes(filters.plateNumber);
    const typeMatch =
      !filters.vehicleType || item.vehicleType === filters.vehicleType;
    const directionMatch =
      !filters.direction || item.direction === filters.direction;
    const timeMatch =
      !timeRange.startTime ||
      (item.timestamp >= timeRange.startTime &&
        item.timestamp <= timeRange.endTime);
    return plateMatch && typeMatch && directionMatch && timeMatch;
  });
});
// èŽ·å–è¡¨æ ¼æ•°æ®
const getTableData = () => {
  pagination.total = filteredData.value.length;
  const start = (pagination.currentPage - 1) * pagination.pageSize;
  const end = start + pagination.pageSize;
  dataList.value = filteredData.value.slice(start, end);
};
// é‡ç½®è¿‡æ»¤å™¨
const resetFilters = () => {
  filters.plateNumber = "";
  filters.vehicleType = "";
  filters.direction = "";
  filters.timeRange = [];
  timeRange.startTime = "";
  timeRange.endTime = "";
  pagination.currentPage = 1;
  getTableData();
};
// åˆ†é¡µå˜åŒ–
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
  pagination.pageSize = limit;
  getTableData();
};
// æ—¶é—´èŒƒå›´å˜åŒ–
const changeTimeRange = (value) => {
  if (value && value.length === 2) {
    timeRange.startTime = value[0];
    timeRange.endTime = value[1];
  } else {
    timeRange.startTime = "";
    timeRange.endTime = "";
  }
  getTableData();
};
// æŸ¥çœ‹è½¦è¾†è¯¦æƒ…
const viewDetail = (row) => {
  selectedVehicle.value = row;
  detailVisible.value = true;
};
// åˆå§‹åŒ–数据
onMounted(() => {
  getTableData();
});
</script>
<style lang="scss" scoped>
.table_list {
  margin-top: unset;
}
.vehicle-stats {
  display: flex;
  gap: 20px;
  margin-bottom: 20px;
  .stat-card {
    flex: 1;
    background: #fff;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    text-align: center;
    .stat-title {
      font-size: 14px;
      color: #606266;
      margin-bottom: 10px;
    }
    .stat-value {
      font-size: 32px;
      font-weight: bold;
      &.in {
        color: #67c23a;
      }
      &.out {
        color: #409eff;
      }
      &.present {
        color: #e6a23c;
      }
      &.total {
        color: #909399;
      }
    }
  }
}
.vehicle-detail {
  .detail-section {
    margin-bottom: 20px;
    h3 {
      font-size: 16px;
      font-weight: bold;
      margin-bottom: 10px;
      color: #303133;
    }
  }
  .vehicle-images {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 15px;
    .image-item {
      background: #f5f7fa;
      padding: 15px;
      border-radius: 8px;
      .image-title {
        font-size: 14px;
        color: #606266;
        margin-bottom: 10px;
        text-align: center;
      }
    }
  }
}
</style>
src/views/equipmentManagement/ledger/index.vue
@@ -63,6 +63,8 @@
          size: pagination.pageSize,
          total: pagination.total,
        }"
        :isShowSummary="true"
        :summaryMethod="summaryMethod"
        @selection-change="handleSelectionChange"
        @pagination="changePage"
      >
@@ -90,6 +92,8 @@
import dayjs from "dayjs";
import QRCode from "qrcode";
import { ref } from "vue";
import { summarizeTable } from "@/utils/summarizeTable";
import {Search} from "@element-plus/icons-vue";
defineOptions({
  name: "设备台账",
@@ -205,6 +209,20 @@
    pagination.pageSize = limit;
  onCurrentChange(page);
};
// åˆè®¡æ–¹æ³•
const summaryMethod = (param) => {
  return summarizeTable(
    param,
    ['number', 'taxIncludingPriceTotal', 'unTaxIncludingPriceTotal', 'taxIncludingPriceUnit'],
    {
      number: { noDecimal: true },
      taxIncludingPriceTotal: { decimalPlaces: 2 },
      unTaxIncludingPriceTotal: { decimalPlaces: 2 }
    }
  );
};
const deleteRow = (id) => {
  ElMessageBox.confirm("此操作将永久删除该文件, æ˜¯å¦ç»§ç»­?", "提示", {
    confirmButtonText: "确定",
src/views/equipmentManagement/repair/index.vue
@@ -143,6 +143,7 @@
import { ElMessageBox, ElMessage } from "element-plus";
import dayjs from "dayjs";
import MaintainModal from "./Modal/MaintainModal.vue";
import {Search} from "@element-plus/icons-vue";
defineOptions({
  name: "设备报修",
src/views/financialManagement/financialReconciliation/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,291 @@
<template>
  <div class="app-container">
    <!-- æœç´¢åŒº -->
    <div class="search_form">
      <el-form :inline="true" :model="searchForm">
        <el-form-item label="往来单位:">
          <el-input v-model="searchForm.counterparty" placeholder="请输入往来单位" clearable style="width: 180px" />
        </el-form-item>
        <el-form-item label="对账状态:">
          <el-select v-model="searchForm.status" placeholder="请选择" clearable style="width: 150px">
            <el-option label="未对账" value="pending" />
            <el-option label="对账中" value="processing" />
            <el-option label="已完成" value="done" />
          </el-select>
        </el-form-item>
        <!-- <el-form-item label="日期:">
          <el-date-picker
            v-model="searchForm.dateRange"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            value-format="YYYY-MM-DD"
            format="YYYY-MM-DD"
            clearable
          />
        </el-form-item> -->
        <el-form-item>
          <el-button type="primary" @click="handleQuery">搜索</el-button>
          <el-button @click="resetQuery">重置</el-button>
        </el-form-item>
      </el-form>
    </div>
    <!-- æ“ä½œåŒº -->
    <div class="actions">
      <el-button type="primary" @click="openDialog('add')">新增</el-button>
      <el-button type="danger" plain @click="handleDelete">删除</el-button>
    </div>
    <!-- è¡¨æ ¼ -->
    <el-table
      :data="tableData"
      border
      v-loading="tableLoading"
      @selection-change="handleSelectionChange"
      style="width: 100%"
      height="calc(100vh - 280px)"
      :row-key="row => row.id"
    >
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column type="index" label="序号" width="60" align="center" />
      <el-table-column prop="accountDate" label="对账日期" width="130" show-overflow-tooltip />
      <el-table-column prop="counterparty" label="往来单位" width="180" show-overflow-tooltip />
      <el-table-column prop="amount" label="对账金额(元)" width="140" show-overflow-tooltip />
      <el-table-column prop="statusText" label="状态" width="120" show-overflow-tooltip />
      <el-table-column prop="remark" label="备注" show-overflow-tooltip />
      <el-table-column fixed="right" label="操作" width="150" align="center">
        <template #default="scope">
          <el-button link type="primary" size="small" @click="openDialog('edit', scope.row)">编辑</el-button>
        </template>
      </el-table-column>
    </el-table>
    <pagination
      v-show="total > 0"
      :total="total"
      layout="total, sizes, prev, pager, next, jumper"
      :page="page.current"
      :limit="page.size"
      @pagination="paginationChange"
    />
    <!-- å¼¹çª— -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="520px" @close="resetForm">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
        <el-form-item label="对账日期:" prop="accountDate">
          <el-date-picker v-model="form.accountDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" style="width: 100%" />
        </el-form-item>
        <el-form-item label="往来单位:" prop="counterparty">
          <el-input v-model="form.counterparty" placeholder="请输入往来单位" />
        </el-form-item>
        <el-form-item label="对账金额(元):" prop="amount">
          <el-input-number v-model="form.amount" :min="0" :precision="2" :step="100" style="width: 100%" />
        </el-form-item>
        <el-form-item label="状态:" prop="status">
          <el-select v-model="form.status" placeholder="请选择" style="width: 100%">
            <el-option label="未对账" value="pending" />
            <el-option label="对账中" value="processing" />
            <el-option label="已完成" value="done" />
          </el-select>
        </el-form-item>
        <el-form-item label="备注:">
          <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="submitForm">确认</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import pagination from '@/components/PIMTable/Pagination.vue'
import {
  getReconciliationPage,
  addReconciliation,
  updateReconciliation,
  deleteReconciliation
} from '@/api/financialManagement/financialReconciliation.js'
const tableData = ref([])
const tableLoading = ref(false)
const selectedRows = ref([])
const dialogVisible = ref(false)
const dialogTitle = ref('新增')
const formRef = ref(null)
const page = reactive({
  current: 1,
  size: 100
})
const total = ref(0)
const searchForm = reactive({
  counterparty: '',
  status: '',
  dateRange: []
})
const form = reactive({
  id: null,
  accountDate: '',
  counterparty: '',
  amount: null,
  status: '',
  remark: ''
})
const rules = {
  accountDate: [{ required: true, message: '请选择对账日期', trigger: 'change' }],
  counterparty: [{ required: true, message: '请输入往来单位', trigger: 'blur' }],
  amount: [{ required: true, message: '请输入对账金额', trigger: 'blur' }],
  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
}
const statusTextMap = {
  pending: '未对账',
  processing: '对账中',
  done: '已完成'
}
// èŽ·å–åˆ—è¡¨
const getList = () => {
  tableLoading.value = true
  const params = {
    ...searchForm,
    current: page.current,
    size: page.size
  }
  if (params.dateRange && params.dateRange.length === 2) {
    params.beginDate = params.dateRange[0]
    params.endDate = params.dateRange[1]
  }
  delete params.dateRange
  getReconciliationPage(params)
    .then(res => {
      if (res.code === 200) {
        tableData.value = (res.data?.records || []).map(item => ({
          ...item,
          statusText: statusTextMap[item.status] || item.status
        }))
        total.value = res.data?.total || 0
      }
    })
    .finally(() => {
      tableLoading.value = false
    })
}
const handleQuery = () => {
  page.current = 1
  getList()
}
const resetQuery = () => {
  searchForm.counterparty = ''
  searchForm.status = ''
  searchForm.dateRange = []
  handleQuery()
}
const paginationChange = (obj) => {
  page.current = obj.page
  page.size = obj.limit
  getList()
}
const handleSelectionChange = (rows) => {
  selectedRows.value = rows
}
const openDialog = (type, row) => {
  dialogTitle.value = type === 'add' ? '新增对账记录' : '编辑对账记录'
  if (type === 'edit' && row) {
    Object.assign(form, row)
  } else {
    resetForm()
  }
  dialogVisible.value = true
}
const resetForm = () => {
  Object.assign(form, {
    id: null,
    date: '',
    counterparty: '',
    amount: null,
    status: '',
    remark: ''
  })
}
const submitForm = () => {
  formRef.value?.validate(valid => {
    if (!valid) return
    const payload = { ...form }
    const request = payload.id ? updateReconciliation(payload) : addReconciliation(payload)
    request
      .then(res => {
        if (res.code === 200) {
          ElMessage.success('提交成功')
          dialogVisible.value = false
          getList()
        }
      })
      .catch(err => {
        ElMessage.error(err.msg || '提交失败')
      })
  })
}
const handleDelete = () => {
  if (!selectedRows.value.length) {
    ElMessage.warning('请选择要删除的记录')
    return
  }
  ElMessageBox.confirm('确认删除选中记录吗?', '提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const ids = selectedRows.value.map(r => r.id)
    deleteReconciliation(ids).then(res => {
      if (res.code === 200) {
        ElMessage.success('删除成功')
        getList()
        selectedRows.value = []
      }
    }).catch(err => {
      ElMessage.error(err.msg || '删除失败')
    })
  }).catch(() => {})
}
onMounted(() => {
  getList()
})
</script>
<style scoped lang="scss">
.app-container {
  padding: 20px;
  .search_form {
    background: #fff;
    padding: 16px;
    border-radius: 6px;
    margin-bottom: 16px;
  }
  .actions {
    margin-bottom: 12px;
  }
}
</style>
src/views/financialManagement/fundsManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,298 @@
<template>
  <div class="app-container">
    <!-- æœç´¢åŒº -->
    <div class="search_form">
      <el-form :inline="true" :model="searchForm">
        <el-form-item label="资金类型:">
          <el-select v-model="searchForm.typeText" placeholder="请选择" clearable style="width: 150px">
            <el-option label="收入" value="收入" />
            <el-option label="支出" value="支出" />
            <el-option label="内部转账" value="内部转账" />
          </el-select>
        </el-form-item>
        <el-form-item label="账户:">
          <el-input v-model="searchForm.account" placeholder="请输入账户" clearable style="width: 160px" />
        </el-form-item>
        <!-- <el-form-item label="日期:">
          <el-date-picker
            v-model="searchForm.dateRange"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            value-format="YYYY-MM-DD"
            format="YYYY-MM-DD"
            clearable
          />
        </el-form-item> -->
        <el-form-item>
          <el-button type="primary" @click="handleQuery">搜索</el-button>
          <el-button @click="resetQuery">重置</el-button>
        </el-form-item>
      </el-form>
    </div>
    <!-- æ“ä½œåŒº -->
    <div class="actions">
      <el-button type="primary" @click="openDialog('add')">新增</el-button>
      <el-button type="danger" plain @click="handleDelete">删除</el-button>
    </div>
    <!-- è¡¨æ ¼ -->
    <el-table
      :data="tableData"
      border
      v-loading="tableLoading"
      @selection-change="handleSelectionChange"
      style="width: 100%"
      height="calc(100vh - 280px)"
      :row-key="row => row.id"
    >
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column type="index" label="序号" width="60" align="center" />
      <el-table-column prop="accountDate" label="日期" width="130" show-overflow-tooltip />
      <el-table-column prop="typeText" label="资金类型" width="120" show-overflow-tooltip />
      <el-table-column prop="amount" label="金额(元)" width="120" show-overflow-tooltip />
      <el-table-column prop="account" label="账户" width="160" show-overflow-tooltip />
      <el-table-column prop="counterparty" label="往来单位" width="180" show-overflow-tooltip />
      <el-table-column prop="remark" label="备注" show-overflow-tooltip />
      <el-table-column fixed="right" label="操作" width="150" align="center">
        <template #default="scope">
          <el-button link type="primary" size="small" @click="openDialog('edit', scope.row)">编辑</el-button>
        </template>
      </el-table-column>
    </el-table>
    <pagination
      v-show="total > 0"
      :total="total"
      layout="total, sizes, prev, pager, next, jumper"
      :page="page.current"
      :limit="page.size"
      @pagination="paginationChange"
    />
    <!-- å¼¹çª— -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="520px" @close="resetForm">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
        <el-form-item label="日期:" prop="accountDate">
          <el-date-picker v-model="form.accountDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" style="width: 100%" />
        </el-form-item>
        <el-form-item label="资金类型:" prop="typeText">
          <el-select v-model="form.typeText" placeholder="请选择" style="width: 100%">
            <el-option label="收入" value="收入" />
            <el-option label="支出" value="支出" />
            <el-option label="内部转账" value="内部转账" />
          </el-select>
        </el-form-item>
        <el-form-item label="金额(元):" prop="amount">
          <el-input-number v-model="form.amount" :min="0" :precision="2" :step="100" style="width: 100%" />
        </el-form-item>
        <el-form-item label="账户:" prop="account">
          <el-input v-model="form.account" placeholder="请输入账户" />
        </el-form-item>
        <el-form-item label="往来单位:" prop="counterparty">
          <el-input v-model="form.counterparty" placeholder="请输入往来单位" />
        </el-form-item>
        <el-form-item label="备注:">
          <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="submitForm">确认</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import pagination from '@/components/PIMTable/Pagination.vue'
import {
  getFundsPage,
  addFunds,
  updateFunds,
  deleteFunds
} from '@/api/financialManagement/fundsManagement.js'
const tableData = ref([])
const tableLoading = ref(false)
const selectedRows = ref([])
const dialogVisible = ref(false)
const dialogTitle = ref('新增')
const formRef = ref(null)
const page = reactive({
  current: 1,
  size: 100
})
const total = ref(0)
const searchForm = reactive({
  typeText: '',
  account: '',
  dateRange: []
})
const form = reactive({
  id: null,
  accountDate: '',
  typeText: '',
  amount: null,
  account: '',
  counterparty: '',
  remark: ''
})
const rules = {
  accountDate: [{ required: true, message: '请选择日期', trigger: 'change' }],
  typeText: [{ required: true, message: '请选择资金类型', trigger: 'change' }],
  amount: [{ required: true, message: '请输入金额', trigger: 'blur' }],
  account: [{ required: true, message: '请输入账户', trigger: 'blur' }]
}
const typeTextMap = {
  income: '收入',
  expense: '支出',
  transfer: '内部转账'
}
// èŽ·å–åˆ—è¡¨
const getList = () => {
  tableLoading.value = true
  const params = {
    ...searchForm,
    current: page.current,
    size: page.size
  }
  // å…¼å®¹åŽç«¯å­—段:若后端使用 typeText,请直接传递;若使用 type,请与后端对齐
  if (params.dateRange && params.dateRange.length === 2) {
    params.beginDate = params.dateRange[0]
    params.endDate = params.dateRange[1]
  }
  delete params.dateRange
  getFundsPage(params)
    .then(res => {
      if (res.code === 200) {
        tableData.value = (res.data?.records || []).map(item => ({
          ...item,
          typeText: item.typeText || typeTextMap[item.typeText] || item.typeText
        }))
        total.value = res.data?.total || 0
      }
    })
    .finally(() => {
      tableLoading.value = false
    })
}
const handleQuery = () => {
  page.current = 1
  getList()
}
const resetQuery = () => {
  searchForm.typeText = ''
  searchForm.account = ''
  searchForm.dateRange = []
  handleQuery()
}
const paginationChange = (obj) => {
  page.current = obj.page
  page.size = obj.limit
  getList()
}
const handleSelectionChange = (rows) => {
  selectedRows.value = rows
}
const openDialog = (type, row) => {
  dialogTitle.value = type === 'add' ? '新增资金记录' : '编辑资金记录'
  if (type === 'edit' && row) {
    Object.assign(form, row)
  } else {
    resetForm()
  }
  dialogVisible.value = true
}
const resetForm = () => {
  Object.assign(form, {
    id: null,
    accountDate: '',
    typeText: '',
    amount: null,
    account: '',
    counterparty: '',
    remark: ''
  })
}
const submitForm = () => {
  formRef.value?.validate(valid => {
    if (!valid) return
    const payload = { ...form }
    const request = payload.id ? updateFunds(payload) : addFunds(payload)
    request
      .then(res => {
        if (res.code === 200) {
          ElMessage.success('提交成功')
          dialogVisible.value = false
          getList()
        }
      })
      .catch(err => {
        ElMessage.error(err.msg || '提交失败')
      })
  })
}
const handleDelete = () => {
  if (!selectedRows.value.length) {
    ElMessage.warning('请选择要删除的记录')
    return
  }
  ElMessageBox.confirm('确认删除选中记录吗?', '提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const ids = selectedRows.value.map(r => r.id)
    deleteFunds(ids).then(res => {
      if (res.code === 200) {
        ElMessage.success('删除成功')
        getList()
        selectedRows.value = []
      }
    }).catch(err => {
      ElMessage.error(err.msg || '删除失败')
    })
  }).catch(() => {})
}
onMounted(() => {
  getList()
})
</script>
<style scoped lang="scss">
.app-container {
  padding: 20px;
  .search_form {
    background: #fff;
    padding: 16px;
    border-radius: 6px;
    margin-bottom: 16px;
  }
  .actions {
    margin-bottom: 12px;
  }
}
</style>
src/views/index.vue
@@ -18,126 +18,126 @@
                        </div>
                    </div>
                </div>
                <div class="data-cards">
                    <div class="data-card sales">
                        <div class="data-title">销售数据</div>
                        <div class="data-num">
                            <div>
                                <div class="data-desc">本月销售额/元</div>
                                <div class="data-value">{{businessInfo.monthSaleMoney}}</div>
                            </div>
                            <div>
                                <div class="data-desc">未开票金额/元</div>
                                <div class="data-value">{{businessInfo.monthSaleHaveMoney}}</div>
                            </div>
                        </div>
                    </div>
                    <div class="data-card purchase">
                        <div class="data-title">采购数据</div>
                        <div class="data-num">
                            <div>
                                <div class="data-desc">本月采购额/元</div>
                                <div class="data-value">{{businessInfo.monthPurchaseMoney}}</div>
                            </div>
                            <div>
                                <div class="data-desc">待付款金额/元</div>
                                <div class="data-value">{{businessInfo.monthPurchaseHaveMoney}}</div>
                            </div>
                        </div>
                    </div>
                    <div class="data-card inventory">
                        <div class="data-title">库存数据</div>
                        <div class="data-num">
                            <div>
                                <div class="data-desc">当前库存总量/ä»¶</div>
                                <div class="data-value">{{businessInfo.inventoryNum}}</div>
                            </div>
                            <div>
                                <div class="data-desc">今日入库/ä»¶</div>
                                <div class="data-value">{{businessInfo.todayInventoryNum}}</div>
                            </div>
                        </div>
                    </div>
                </div>
<!--                <div class="data-cards">-->
<!--                    <div class="data-card sales">-->
<!--                        <div class="data-title">销售数据</div>-->
<!--                        <div class="data-num">-->
<!--                            <div>-->
<!--                                <div class="data-desc">本月销售额/元</div>-->
<!--                                <div class="data-value">{{businessInfo.monthSaleMoney}}</div>-->
<!--                            </div>-->
<!--                            <div>-->
<!--                                <div class="data-desc">未开票金额/元</div>-->
<!--                                <div class="data-value">{{businessInfo.monthSaleHaveMoney}}</div>-->
<!--                            </div>-->
<!--                        </div>-->
<!--                        -->
<!--                    </div>-->
<!--                    <div class="data-card purchase">-->
<!--                        <div class="data-title">采购数据</div>-->
<!--                        <div class="data-num">-->
<!--                            <div>-->
<!--                                <div class="data-desc">本月采购额/元</div>-->
<!--                                <div class="data-value">{{businessInfo.monthPurchaseMoney}}</div>-->
<!--                            </div>-->
<!--                            <div>-->
<!--                                <div class="data-desc">待付款金额/元</div>-->
<!--                                <div class="data-value">{{businessInfo.monthPurchaseHaveMoney}}</div>-->
<!--                            </div>-->
<!--                        </div>-->
<!--                    </div>-->
<!--                    <div class="data-card inventory">-->
<!--                        <div class="data-title">库存数据</div>-->
<!--                        <div class="data-num">-->
<!--                            <div>-->
<!--                                <div class="data-desc">当前库存总量/ä»¶</div>-->
<!--                                <div class="data-value">{{businessInfo.inventoryNum}}</div>-->
<!--                            </div>-->
<!--                            <div>-->
<!--                                <div class="data-desc">今日入库/ä»¶</div>-->
<!--                                <div class="data-value">{{businessInfo.todayInventoryNum}}</div>-->
<!--                            </div>-->
<!--                        </div>-->
<!--                    </div>-->
<!--                </div>-->
            </div>
            <!-- å³ï¼šå¾…办事项 -->
            <div class="todo-panel">
                <div class="section-title">待办事项</div>
                <ul class="todo-list" v-if="todoList.length > 0">
                    <li v-for="item in todoList" :key="item.id">
                        <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px">
                            <div style="display: flex;justify-content: space-between;align-items: center;">
                                <div class="todo-title">待办编号:{{item.approveId}}</div>
                                <div class="todo-division">部门:{{item.approveDeptName}}</div>
                                <div class="todo-time">{{item.approveTime}}</div>
                            </div>
                            <div class="todo-division">待办事由:{{item.approveReason}}</div>
                        </div>
                    </li>
                </ul>
                <div v-else style="text-align: center">
                    æš‚无数据
                </div>
            </div>
        </div>
        <!-- ä¸­éƒ¨æ¨ªå‘两栏 -->
        <div class="dashboard-row">
            <div class="main-panel">
                <div class="section-title">客户合同金额分析</div>
                <div class="contract-summary">
                    <div class="contract-info">
                        <img src="../assets/images/khtitle.png" alt="" style="width: 42px"/>
                        <div class="contract-card">
                            <div class="contract-name">总合同金额(元)</div>
                            <div class="contract-meta">
                                <div class="main-amount">{{sum}}</div>
                                <div>周同比: <span class="up">{{yny}}% </span> æ—¥çŽ¯æ¯”: <span class="up">{{chain}}% </span></div>
                            </div>
                        </div>
                    </div>
                </div>
                <div style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 180px;margin-top: 20px">
                    <div>
                        <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie"
                                         :series="materialPieSeries"
                                         :tooltip="pieTooltip"></Echarts>
                    </div>
                    <ul class="contract-list">
                        <li v-for="item in materialPieSeries[0].data" :key="item.name">
                            <div style="display: flex;align-items: center;justify-content: space-between;width: 100%">
                                <div class="line" :style="{color: item.itemStyle.color}">●{{item.name}}</div>
                                <div style="width: 70px">{{item.rate}}%</div>
                                <div>ï¿¥{{item.value}}</div>
                            </div>
                        </li>
                    </ul>
                </div>
            </div>
            <div class="main-panel">
                <div style="display: flex;justify-content: space-between;">
                    <div class="section-title">应收应付统计</div>
                    <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable">
                        <el-radio-button label="按周" :value="1" />
                        <el-radio-button label="按月" :value="2" />
                        <el-radio-button label="按季度" :value="3" />
                    </el-radio-group>
                </div>
                <Echarts ref="chart"
                                 :color="barColors2"
                                 :chartStyle="chartStyle"
                                 :grid="grid"
                                 :series="barSeries"
                                 :tooltip="tooltip"
                                 :xAxis="xAxis"
                                 :yAxis="yAxis"
                                 style="height: 260px"></Echarts>
            </div>
        </div>
<!--            <div class="todo-panel">-->
<!--                <div class="section-title">待办事项</div>-->
<!--                <ul class="todo-list" v-if="todoList.length > 0">-->
<!--                    <li v-for="item in todoList" :key="item.id">-->
<!--                        <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px">-->
<!--                            <div style="display: flex;justify-content: space-between;align-items: center;">-->
<!--                                <div class="todo-title">流程编号:{{item.approveId}}</div>-->
<!--                                <div class="todo-division">申请部门:{{item.approveDeptName}}</div>-->
<!--                                <div class="todo-time">{{item.approveTime}}</div>-->
<!--                            </div>-->
<!--                            <div class="todo-division">审批事由:{{item.approveReason}}</div>-->
<!--                        </div>-->
<!--                    </li>-->
<!--                </ul>-->
<!--                <div v-else style="text-align: center">-->
<!--                    æš‚无数据-->
<!--                </div>-->
<!--            </div>-->
<!--        </div>-->
<!--        -->
<!--        &lt;!&ndash; ä¸­éƒ¨æ¨ªå‘两栏 &ndash;&gt;-->
<!--        <div class="dashboard-row">-->
<!--            <div class="main-panel">-->
<!--                <div class="section-title">客户合同金额分析</div>-->
<!--                <div class="contract-summary">-->
<!--                    <div class="contract-info">-->
<!--                        <img src="../assets/images/khtitle.png" alt="" style="width: 42px"/>-->
<!--                        <div class="contract-card">-->
<!--                            <div class="contract-name">总合同金额(元)</div>-->
<!--                            <div class="contract-meta">-->
<!--                                <div class="main-amount">{{sum}}</div>-->
<!--                                <div>周同比: <span class="up">{{yny}}% </span> æ—¥çŽ¯æ¯”: <span class="up">{{chain}}% </span></div>-->
<!--                            </div>-->
<!--                        </div>-->
<!--                    </div>-->
<!--                </div>-->
<!--                <div style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 180px;margin-top: 20px">-->
<!--                    <div>-->
<!--                        <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie"-->
<!--                                         :series="materialPieSeries"-->
<!--                                         :tooltip="pieTooltip"></Echarts>-->
<!--                    </div>-->
<!--                    <ul class="contract-list">-->
<!--                        <li v-for="item in materialPieSeries[0].data" :key="item.name">-->
<!--                            <div style="display: flex;align-items: center;justify-content: space-between;width: 100%">-->
<!--                                <div class="line" :style="{color: item.itemStyle.color}">●{{item.name}}</div>-->
<!--                                <div style="width: 70px">{{item.rate}}%</div>-->
<!--                                <div>ï¿¥{{item.value}}</div>-->
<!--                            </div>-->
<!--                        </li>-->
<!--                    </ul>-->
<!--                </div>-->
<!--            </div>-->
<!--            <div class="main-panel">-->
<!--                <div style="display: flex;justify-content: space-between;">-->
<!--                    <div class="section-title">应收应付统计</div>-->
<!--&lt;!&ndash;                    <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable">&ndash;&gt;-->
<!--&lt;!&ndash;                        <el-radio-button label="按周" :value="1" />&ndash;&gt;-->
<!--&lt;!&ndash;                        <el-radio-button label="按月" :value="2" />&ndash;&gt;-->
<!--&lt;!&ndash;                        <el-radio-button label="按季度" :value="3" />&ndash;&gt;-->
<!--&lt;!&ndash;                    </el-radio-group>&ndash;&gt;-->
<!--                </div>-->
<!--                <Echarts ref="chart"-->
<!--                                 :color="barColors2"-->
<!--                                 :chartStyle="chartStyle"-->
<!--                                 :grid="grid"-->
<!--                                 :series="barSeries"-->
<!--                                 :tooltip="tooltip"-->
<!--                                 :xAxis="xAxis"-->
<!--                                 :yAxis="yAxis"-->
<!--                                 style="height: 260px"></Echarts>-->
<!--            </div>-->
<!--        </div>-->
        
        <!-- åº•部横向两栏 -->
        <div class="dashboard-row">
<!--        <div class="dashboard-row">-->
<!--            <div class="main-panel">-->
<!--                <div class="section-title">质量统计</div>-->
<!--                <div class="quality-cards">-->
@@ -155,11 +155,11 @@
<!--                                 :yAxis="yAxis1"-->
<!--                                 style="height: 260px"></Echarts>-->
<!--            </div>-->
            <div class="main-panel">
                <div class="section-title">回款与开票分析</div>
                <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries"
                                 :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" style="height: 270px;"></Echarts>
            </div>
<!--            <div class="main-panel">-->
<!--                <div class="section-title">回款与开票分析</div>-->
<!--                <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries"-->
<!--                                 :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" style="height: 270px;"></Echarts>-->
<!--            </div>-->
        </div>
    </div>
</template>
@@ -506,7 +506,7 @@
    min-width: 0;
    background-color: #EFF2FB; /* ä½¿ç”¨æŒ‡å®šçš„背景颜色 */
    background-image: url("../assets/images/denglu.png");
    background-size: cover;
    background-size: 100% 260%;
    background-position: center;
    background-repeat: no-repeat;
    border-radius: 12px;
src/views/inventoryManagement/receiptManagement/components/formDia.vue
@@ -78,6 +78,7 @@
  selectProductRecordListByPuechaserId
} from "@/api/inventoryManagement/stockIn.js";
import { purchaseListPage } from "@/api/procurementManagement/procurementLedger.js";
import { getCurrentDate } from "@/utils/index.js";
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
src/views/inventoryManagement/receiptManagement/components/formDiaManual.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,310 @@
<template>
  <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '新增自定义入库' : '编辑自定义入库'" width="70%"
    @close="closeDia">
    <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
      <div style="margin-bottom: 10px;" v-if="operationType === 'add'">
        <el-button type="primary" @click="addProductRow">新增</el-button>
      </div>
      <el-table
        :data="productList"
        border
        v-loading="loadingProducts"
      >
        <el-table-column
          align="center"
          label="序号"
          type="index"
          width="60"
        />
        <el-table-column label="产品大类" prop="productCategory" width="200">
          <template #default="scope">
            <el-input v-model="scope.row.productCategory" placeholder="请输入产品大类" />
          </template>
        </el-table-column>
        <el-table-column label="规格型号" prop="specificationModel" width="200">
          <template #default="scope">
            <el-input v-model="scope.row.specificationModel" placeholder="请输入规格型号" />
          </template>
        </el-table-column>
        <el-table-column label="单位" prop="unit" width="100">
          <template #default="scope">
            <el-input v-model="scope.row.unit" placeholder="请输入单位" />
          </template>
        </el-table-column>
        <el-table-column label="供应商" prop="supplierName" width="200">
          <template #default="scope">
            <el-input v-model="scope.row.supplierName" placeholder="请输入供应商" />
          </template>
        </el-table-column>
        <el-table-column label="物品类型" prop="itemType" width="150">
          <template #default="scope">
            <el-input v-model="scope.row.itemType" placeholder="请输入物品类型" />
          </template>
        </el-table-column>
        <el-table-column label="入库数量" prop="inboundNum" width="150">
          <template #default="scope">
            <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.inboundNum" @change="() => calculateTotalPrice(scope.row)" />
          </template>
        </el-table-column>
        <el-table-column label="入库日期" prop="inboundDate" width="180">
          <template #default="scope">
            <el-date-picker
              v-model="scope.row.inboundDate"
              type="date"
              placeholder="请选择入库日期"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              style="width: 100%"
            />
          </template>
        </el-table-column>
        <el-table-column label="单价(元)" prop="taxInclusiveUnitPrice" width="150">
          <template #default="scope">
            <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.taxInclusiveUnitPrice" @change="() => calculateTotalPrice(scope.row)" />
          </template>
        </el-table-column>
        <el-table-column
           label="总价(元)"
           prop="taxInclusiveTotalPrice"
           width="150"
         >
        </el-table-column>
        <el-table-column label="操作" width="80" v-if="operationType === 'add'">
          <template #default="scope">
            <el-button type="danger" size="small" @click="removeProductRow(scope.$index)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-form>
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="submitForm">确认</el-button>
        <el-button @click="closeDia">取消</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { ref, reactive, toRefs, getCurrentInstance } from 'vue'
import useUserStore from '@/store/modules/user'
import {
  addStockInCustom,
  updateStockInCustom,
} from "@/api/inventoryManagement/stockIn.js";
import { getCurrentDate } from "@/utils/index.js";
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close', 'success'])
const operationType = ref('')// æ“ä½œç±»åž‹: 'add' æˆ– 'edit'
const dialogFormVisible = ref(false)// å¼¹æ¡†æ˜¾ç¤ºçŠ¶æ€
const productList = ref([]);// äº§å“åˆ—表数据
const loadingProducts = ref(false);// äº§å“åŠ è½½çŠ¶æ€
const loading = ref(false);
function formatDateTime(date = new Date(), includeTime = true) {
  const d = new Date(date);
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  if (!includeTime) {
    return `${year}-${month}-${day}`;
  }
  const hours = String(d.getHours()).padStart(2, '0');
  const minutes = String(d.getMinutes()).padStart(2, '0');
  const seconds = String(d.getSeconds()).padStart(2, '0');
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
const taxRateOptions = [
  { label: '1', value: 1 },
  { label: '6', value: 6 },
  { label: '13', value: 13 },
]
const data = reactive({
  form: {
    id: null,
    supplierId: null,       // ä¾›åº”商ID
    supplierName: '',       // ä¾›åº”商名称
    recorderId: userStore.userId, // å½•入人ID
    recorderName: userStore.name, // å½•入人姓名
    entryDate: getCurrentDate(),  // å½•入日期
    remark: '',             // å¤‡æ³¨
  },
  rules: {
    supplierName: [{ required: true, message: "请输入供应商名称", trigger: "blur" }]
  }
})
const { form, rules } = toRefs(data)
// æ–°å¢žäº§å“è¡Œ
const addProductRow = () => {
  productList.value.push({
    id: null,
    productCategory: '',
    specificationModel: '',
    unit: '',
    supplierName: form.value.supplierName || '',
    itemType: '',
    inboundNum: 0,
    inboundDate: getCurrentDate(), // é»˜è®¤å½“天日期
    // quantityStock: 0,
    taxInclusiveUnitPrice: 0,
    taxInclusiveTotalPrice: 0,
    taxRate: null,
    taxExclusiveTotalPrice: 0,
  });
};
// åˆ é™¤äº§å“è¡Œ
const removeProductRow = (index) => {
  productList.value.splice(index, 1);
};
// è®¡ç®—总价(根据数量、单价和含税单价)
const calculateTotalPrice = (row) => {
  // è®¡ç®—普通总价:inboundNum * taxInclusiveUnitPrice
  const quantity = Number(row.inboundNum || 0);
  const taxInclusiveUnitPrice = Number(row.taxInclusiveUnitPrice || 0);
  row.taxInclusiveTotalPrice = quantity * taxInclusiveUnitPrice;
  calculateExclusivePrice(row);
};
// è®¡ç®—不含税总价(根据含税总价和税率)
const calculateExclusivePrice = (row) => {
  const taxInclusiveTotalPrice = Number(row.taxInclusiveTotalPrice || 0);
  const taxRate = Number(row.taxRate || 0);
  row.taxExclusiveTotalPrice = taxInclusiveTotalPrice / (1 + taxRate / 100);
};
const submitForm = async () => {
  try {
    await proxy.$refs.formRef.validate()
    if (!productList.value.length) {
      proxy.$modal.msgError('请至少添加一条产品数据')
      return
    }
    // éªŒè¯è‡ªå®šä¹‰æ·»åŠ çš„æ•°æ®å¿…å¡«å­—æ®µ
    for (let i = 0; i < productList.value.length; i++) {
      const product = productList.value[i];
      if (!product.productCategory || !product.specificationModel || !product.unit) {
        proxy.$modal.msgError(`第${i + 1}行产品数据未填写完整(产品大类、规格型号、单位为必填)`)
        return
      }
      if (!product.itemType) {
        proxy.$modal.msgError(`第${i + 1}行请选择物品类型`)
        return
      }
      if (!product.inboundDate) {
        proxy.$modal.msgError(`第${i + 1}行请选择入库日期`)
        return
      }
      const stock = Number(product?.inboundNum ?? 0);
      if (!Number.isFinite(stock) || stock <= 0) {
        proxy.$modal.msgError(`第${i + 1}行本次入库数量需大于0`)
        return
      }
    }
    const payloadList = productList.value.map(product => ({
      id: product.id ?? null,
            inboundNum: Number(product.inboundNum),
      productCategory: product.productCategory,
      specificationModel: product.specificationModel,
      unit: product.unit,
      supplierName: product.supplierName || form.value.supplierName,
      itemType: product.itemType,
      inboundDate: formatDateTime(product.inboundDate, false),
      taxRate: Number(product.taxRate || 0),
      taxExclusiveTotalPrice: Number(product.taxExclusiveTotalPrice || 0),
            taxInclusiveUnitPrice: Number(product.taxInclusiveUnitPrice || 0),
            taxInclusiveTotalPrice: Number(product.taxInclusiveTotalPrice || 0),
    }));
    loading.value = true
    if (operationType.value === 'edit') {
      const editPayload = payloadList[0]
      await updateStockInCustom(editPayload)
    } else {
      await addStockInCustom(payloadList)
    }
    proxy.$modal.msgSuccess(operationType.value === 'edit' ? '编辑自定义入库成功' : '新增自定义入库成功')
    closeDia()
    emit('success')
  } catch (error) {
    console.error('提交失败:', error)
    if (!error.errors) {
      proxy.$modal.msgError('操作失败,请重试')
    }
  } finally {
    loading.value = false
  }
}
const closeDia = () => {
  proxy.$refs.formRef.resetFields()
  dialogFormVisible.value = false
  productList.value = []
  emit('close')
}
const openDialog = async (type, row) => {
  operationType.value = type
  dialogFormVisible.value = true
  if (type === 'add') {
    form.value = {
      id: null,
      supplierId: null,
      supplierName: '',
      recorderId: userStore.userId,
      recorderName: userStore.name,
      entryDate: getCurrentDate(),
      remark: ''
    }
    productList.value = []
  } else {
    // ç¼–辑模式:将行数据填充到表格中以支持修改
    form.value = {
      id: row?.id ?? null,
      supplierId: row?.supplierId ?? null,
      supplierName: row?.supplierName ?? '',
      recorderId: userStore.userId,
      recorderName: userStore.name,
      entryDate: getCurrentDate(),
      remark: row?.remark ?? ''
    }
    productList.value = [{
      id: row?.id ?? null,
      productCategory: row?.productCategory ?? '',
      specificationModel: row?.specificationModel ?? '',
      unit: row?.unit ?? '',
      supplierName: row?.supplierName ?? '',
      itemType: row?.itemType ?? '',
      inboundNum: Number(row?.inboundNum ?? row?.inboundQuantity ?? 0),
      inboundDate: row?.inboundDate ?? row?.createTime ?? '',
      taxRate: Number(row?.taxRate ?? 0),
      taxInclusiveUnitPrice: Number(row?.taxInclusiveUnitPrice ?? 0),
      taxInclusiveTotalPrice: Number(row?.taxInclusiveTotalPrice ?? 0),
      taxExclusiveTotalPrice: Number(row?.taxExclusiveTotalPrice ?? 0),
    }]
  }
}
defineExpose({
  openDialog,
})
</script>
<style scoped lang="scss"></style>
src/views/inventoryManagement/receiptManagement/components/formDiaProduct.vue
@@ -81,6 +81,7 @@
import {
    addStockInCustom, updateProduct
} from "@/api/inventoryManagement/stockIn.js";
import { getCurrentDate } from "@/utils/index.js";
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
src/views/inventoryManagement/stockManagement/components/FormDiaManual.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,163 @@
<template>
  <el-dialog :model-value="dialogFormVisible" :title="operationType === 'add' ? '新增材料库存' : '编辑材料库存'" width="70%"
    @update:model-value="$emit('update:dialogFormVisible', $event)" @close="closeDia">
    <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="产品大类:" prop="productCategory">
            <el-input disabled v-model="form.productCategory" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="规格型号:" prop="specificationModel">
            <el-input disabled v-model="form.specificationModel" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="单位:" prop="unit">
            <el-input disabled v-model="form.unit" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="物品类型:" prop="itemType">
            <el-input disabled v-model="form.itemType" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="入库时间:" prop="createTime">
            <el-date-picker style="width: 100%" v-model="form.createTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
                            type="date" placeholder="请选择" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="库存数量:" prop="inboundNum">
            <el-input v-model="form.inboundNum" placeholder="请输入" clearable @input="calculateTotalPrice" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="预警值:" prop="warnNum">
            <el-input-number v-model="form.warnNum" placeholder="请输入预警值" clearable min="0" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="已出库数量:" prop="totalInboundNum">
            <el-input disabled v-model="form.totalInboundNum" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="待出库数量:" prop="inboundNum0">
            <el-input disabled v-model="form.inboundNum0" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="单价(元):" prop="taxInclusiveUnitPrice">
            <el-input v-model="form.taxInclusiveUnitPrice" placeholder="请输入" clearable @input="calculateTotalPrice" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="总价(元):" prop="taxInclusiveTotalPrice">
            <el-input disabled v-model="form.taxInclusiveTotalPrice" placeholder="自动计算" clearable />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="submitForm">确认</el-button>
        <el-button @click="closeDia">取消</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { ref, reactive, toRefs, watch } from 'vue'
const props = defineProps({
  dialogFormVisible: Boolean,
  operationType: String,
  formData: Object
})
const emit = defineEmits(['update:dialogFormVisible', 'submit', 'close'])
const formRef = ref()
const data = reactive({
  form: {
    productCategory: '',
    specificationModel: '',
    unit: '',
    itemType: '',
    createTime: '',
    inboundNum: '',
    totalInboundNum: '',
    inboundNum0: '',
    taxInclusiveUnitPrice: '',
    taxInclusiveTotalPrice: '',
    warnNum: ''
  },
  rules: {
    productCategory: [{ required: true, message: '请输入产品大类', trigger: 'blur' }],
    specificationModel: [{ required: true, message: '请输入规格型号', trigger: 'blur' }],
    unit: [{ required: true, message: '请输入单位', trigger: 'blur' }],
    itemType: [{ required: true, message: '请输入物品类型', trigger: 'blur' }],
    createTime: [{ required: true, message: '请选择入库时间', trigger: 'change' }],
    inboundNum: [{ required: true, message: '请输入库存数量', trigger: 'blur' }],
    taxInclusiveUnitPrice: [{ required: true, message: '请输入单价', trigger: 'blur' }],
    warnNum: [{ required: true, message: '请输入预警值', trigger: 'blur' }]
  }
})
const { form, rules } = toRefs(data)
// è®¡ç®—总价:总价 = å•ä»· Ã— å‰©ä½™åº“å­˜
const calculateTotalPrice = () => {
  const unitPrice = parseFloat(form.value.taxInclusiveUnitPrice) || 0
  const stockQuantity = parseFloat(form.value.inboundNum) || 0 // åº“存数量
  const outboundQuantity = parseFloat(form.value.totalInboundNum) || 0 // å·²å‡ºåº“数量
  const remainingStock = stockQuantity - outboundQuantity // å‰©ä½™åº“å­˜
  form.value.taxInclusiveTotalPrice = (unitPrice * remainingStock).toFixed(2)
}
// ç›‘听formData变化
watch(() => props.formData, (newVal) => {
  if (newVal) {
    form.value = { ...newVal }
    // æ•°æ®å˜åŒ–后重新计算总价
    calculateTotalPrice()
  }
}, { immediate: true })
// æäº¤è¡¨å•
const submitForm = () => {
  formRef.value.validate(valid => {
    if (valid) {
      emit('submit', form.value)
    }
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  emit('close')
  emit('update:dialogFormVisible', false)
}
</script>
<style scoped lang="scss">
.dialog-footer {
  text-align: center;
}
</style>
src/views/inventoryManagement/stockManagement/components/FormDiaProduction.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,147 @@
<template>
  <el-dialog :model-value="dialogFormVisible" :title="operationType === 'add' ? '新增成品库存' : '编辑成品库存'" width="70%"
    @update:model-value="$emit('update:dialogFormVisible', $event)" @close="closeDia">
    <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="产品大类:" prop="productCategory">
            <el-input disabled v-model="form.productCategory" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="规格型号:" prop="specificationModel">
            <el-input disabled v-model="form.specificationModel" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="单位:" prop="unit">
            <el-input disabled v-model="form.unit" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="入库时间:" prop="createTime">
            <el-date-picker style="width: 100%" v-model="form.createTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
                            type="date" placeholder="请选择" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="库存数量:" prop="inboundNum">
            <el-input v-model="form.inboundNum" placeholder="请输入" clearable @input="calculateTotalPrice" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="已出库数量:" prop="totalInboundNum">
            <el-input disabled v-model="form.totalInboundNum" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="待出库数量:" prop="inboundNum0">
            <el-input disabled v-model="form.inboundNum0" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="单价(元):" prop="unitPrice">
            <el-input v-model="form.unitPrice" placeholder="请输入" clearable @input="calculateTotalPrice" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="总价(元):" prop="totalPrice">
            <el-input disabled v-model="form.totalPrice" placeholder="自动计算" clearable />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="submitForm">确认</el-button>
        <el-button @click="closeDia">取消</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { ref, reactive, toRefs, watch } from 'vue'
const props = defineProps({
  dialogFormVisible: Boolean,
  operationType: String,
  formData: Object
})
const emit = defineEmits(['update:dialogFormVisible', 'submit', 'close'])
const formRef = ref()
const data = reactive({
  form: {
    productCategory: '',
    specificationModel: '',
    unit: '',
    createTime: '',
    inboundNum: '',
    totalInboundNum: '',
    inboundNum0: '',
    unitPrice: '',
    totalPrice: ''
  },
  rules: {
    productCategory: [{ required: true, message: '请输入产品大类', trigger: 'blur' }],
    specificationModel: [{ required: true, message: '请输入规格型号', trigger: 'blur' }],
    unit: [{ required: true, message: '请输入单位', trigger: 'blur' }],
    createTime: [{ required: true, message: '请选择入库时间', trigger: 'change' }],
    inboundNum: [{ required: true, message: '请输入库存数量', trigger: 'blur' }],
    unitPrice: [{ required: true, message: '请输入单价', trigger: 'blur' }]
  }
})
const { form, rules } = toRefs(data)
// è®¡ç®—总价:总价 = å•ä»· Ã— å‰©ä½™åº“å­˜
const calculateTotalPrice = () => {
  const unitPrice = parseFloat(form.value.unitPrice) || 0
  const stockQuantity = parseFloat(form.value.inboundNum) || 0 // åº“存数量
  const outboundQuantity = parseFloat(form.value.totalInboundNum) || 0 // å·²å‡ºåº“数量
  const remainingStock = stockQuantity - outboundQuantity // å‰©ä½™åº“å­˜
  form.value.totalPrice = (unitPrice * remainingStock).toFixed(2)
}
// ç›‘听formData变化
watch(() => props.formData, (newVal) => {
  if (newVal) {
    form.value = { ...newVal }
    // æ•°æ®å˜åŒ–后重新计算总价
    calculateTotalPrice()
  }
}, { immediate: true })
// æäº¤è¡¨å•
const submitForm = () => {
  formRef.value.validate(valid => {
    if (valid) {
      emit('submit', form.value)
    }
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  emit('close')
  emit('update:dialogFormVisible', false)
}
</script>
<style scoped lang="scss">
.dialog-footer {
  text-align: center;
}
</style>
src/views/inventoryManagement/stockManagement/components/FormDiaPurchase.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,147 @@
<template>
  <el-dialog :model-value="dialogFormVisible" :title="operationType === 'add' ? '新增原料库存' : '编辑原料库存'" width="70%"
    @update:model-value="$emit('update:dialogFormVisible', $event)" @close="closeDia">
    <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="产品大类:" prop="productCategory">
            <el-input disabled v-model="form.productCategory" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="规格型号:" prop="specificationModel">
            <el-input disabled v-model="form.specificationModel" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="单位:" prop="unit">
            <el-input disabled v-model="form.unit" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="入库时间:" prop="createTime">
            <el-date-picker style="width: 100%" v-model="form.createTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
                            type="date" placeholder="请选择" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="库存数量:" prop="inboundNum">
            <el-input v-model="form.inboundNum" placeholder="请输入" clearable @input="calculateTotalPrice" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="已出库数量:" prop="totalInboundNum">
            <el-input disabled v-model="form.totalInboundNum" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="待出库数量:" prop="inboundNum0">
            <el-input disabled v-model="form.inboundNum0" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="含税单价(元):" prop="taxInclusiveUnitPrice">
            <el-input v-model="form.taxInclusiveUnitPrice" placeholder="请输入" clearable @input="calculateTotalPrice" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="含税总价(元):" prop="taxInclusiveTotalPrice">
            <el-input disabled v-model="form.taxInclusiveTotalPrice" placeholder="自动计算" clearable />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="submitForm">确认</el-button>
        <el-button @click="closeDia">取消</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { ref, reactive, toRefs, watch } from 'vue'
const props = defineProps({
  dialogFormVisible: Boolean,
  operationType: String,
  formData: Object
})
const emit = defineEmits(['update:dialogFormVisible', 'submit', 'close'])
const formRef = ref()
const data = reactive({
  form: {
    productCategory: '',
    specificationModel: '',
    unit: '',
    createTime: '',
    inboundNum: '',
    totalInboundNum: '',
    inboundNum0: '',
    taxInclusiveUnitPrice: '',
    taxInclusiveTotalPrice: ''
  },
  rules: {
    productCategory: [{ required: true, message: '请输入产品大类', trigger: 'blur' }],
    specificationModel: [{ required: true, message: '请输入规格型号', trigger: 'blur' }],
    unit: [{ required: true, message: '请输入单位', trigger: 'blur' }],
    createTime: [{ required: true, message: '请选择入库时间', trigger: 'change' }],
    inboundNum: [{ required: true, message: '请输入库存数量', trigger: 'blur' }],
    taxInclusiveUnitPrice: [{ required: true, message: '请输入含税单价', trigger: 'blur' }]
  }
})
const { form, rules } = toRefs(data)
// è®¡ç®—总价:含税总价 = å«ç¨Žå•ä»· Ã— å‰©ä½™åº“å­˜
const calculateTotalPrice = () => {
  const unitPrice = parseFloat(form.value.taxInclusiveUnitPrice) || 0
  const stockQuantity = parseFloat(form.value.inboundNum) || 0 // åº“存数量
  const outboundQuantity = parseFloat(form.value.totalInboundNum) || 0 // å·²å‡ºåº“数量
  const remainingStock = stockQuantity - outboundQuantity // å‰©ä½™åº“å­˜
  form.value.taxInclusiveTotalPrice = (unitPrice * remainingStock).toFixed(2)
}
// ç›‘听formData变化
watch(() => props.formData, (newVal) => {
  if (newVal) {
    form.value = { ...newVal }
    // æ•°æ®å˜åŒ–后重新计算总价
    calculateTotalPrice()
  }
}, { immediate: true })
// æäº¤è¡¨å•
const submitForm = () => {
  formRef.value.validate(valid => {
    if (valid) {
      emit('submit', form.value)
    }
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  emit('close')
  emit('update:dialogFormVisible', false)
}
</script>
<style scoped lang="scss">
.dialog-footer {
  text-align: center;
}
</style>
src/views/inventoryManagement/stockWarningLedger/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,347 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="产品大类:">
          <el-input
            v-model="searchForm.productCategory"
            placeholder="请输入产品大类"
            clearable
            style="width: 200px"
          />
        </el-form-item>
        <el-form-item label="规格型号:">
          <el-input
            v-model="searchForm.specificationModel"
            placeholder="请输入规格型号"
            clearable
            style="width: 200px"
          />
        </el-form-item>
        <el-form-item label="预警状态:">
          <el-select
            v-model="searchForm.warningStatus"
            placeholder="请选择预警状态"
            clearable
            style="width: 150px"
          >
            <el-option label="已预警" value="已预警" />
            <el-option label="正常" value="正常" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleQuery">搜索</el-button>
          <el-button @click="resetQuery">重置</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div class="table_list">
      <div class="actions"></div>
      <el-table
        :data="tableData"
        border
        v-loading="tableLoading"
        style="width: 100%"
        height="calc(100vh - 280px)"
      >
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="批次号" prop="code" width="130" show-overflow-tooltip />
        <el-table-column label="产品大类" prop="productCategory" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="specificationModel" show-overflow-tooltip />
        <el-table-column label="当前库存" prop="currentStock" width="120" show-overflow-tooltip>
          <template #default="scope">
            <span :class="getStockClass(scope.row)">{{ scope.row.currentStock || 0 }}</span>
          </template>
        </el-table-column>
        <el-table-column label="最低库存" prop="warnNum" width="120" show-overflow-tooltip />
        <el-table-column label="预警级别" prop="warningLevel" width="100" show-overflow-tooltip>
          <template #default="scope">
            <el-tag :type="getWarningLevelTag(scope.row.warningLevel)">
              {{ scope.row.warningLevel || '-' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="预警状态" prop="warningStatus" width="100" show-overflow-tooltip>
          <template #default="scope">
            <el-tag :type="scope.row.warningStatus === '已预警' ? 'danger' : 'success'">
              {{ scope.row.warningStatus || '正常' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="预警时间" prop="warningTime" width="150" show-overflow-tooltip />
        <el-table-column label="预计缺货时间" prop="expectedShortageTime" width="150" show-overflow-tooltip>
          <template #default="scope">
            <div v-if="scope.row.expectedShortageTime">
              <div v-if="getCountdown(scope.row.expectedShortageTime).isExpired" class="countdown-expired">
                <el-tag type="danger">已缺货</el-tag>
              </div>
              <div v-else class="countdown-timer">
                <span :class="getCountdownClass(scope.row.expectedShortageTime)">
                  {{ getCountdown(scope.row.expectedShortageTime).text }}
                </span>
              </div>
            </div>
            <span v-else>-</span>
          </template>
        </el-table-column>
      </el-table>
      <pagination
        v-show="total > 0"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="page.current"
        :limit="page.size"
        @pagination="paginationChange"
      />
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import pagination from '@/components/PIMTable/Pagination.vue'
import {
  getStockWarningLedgerPage
} from '@/api/inventoryManagement/stockWarningLedger.js'
// å“åº”式数据
const tableData = ref([])
const tableLoading = ref(false)
const total = ref(0)
// åˆ†é¡µå‚æ•°
const page = reactive({
  current: 1,
  size: 100
})
// æœç´¢è¡¨å•
const searchForm = reactive({
  productCategory: '',
  specificationModel: '',
  warningStatus: ''
})
// èŽ·å–åˆ—è¡¨æ•°æ®
const getList = () => {
  tableLoading.value = true
  const params = {
    ...page,
    ...searchForm
  }
  getStockWarningLedgerPage(params)
    .then(res => {
      tableLoading.value = false
      if (res.code === 200) {
        tableData.value = res.data.records || []
        total.value = res.data.total || 0
        // è®¡ç®—预警级别和状态
        tableData.value = tableData.value.map(item => {
          const currentStock = parseFloat(item.inboundNum0 || item.currentStock || 0)
          const warnNum = parseFloat(item.warnNum || 0)
          const safetyStock = parseFloat(item.safetyStock || warnNum * 1.2)
          // è®¡ç®—预警级别
          if (currentStock <= 0) {
            item.warningLevel = '紧急'
            item.warningStatus = '已预警'
          } else if (currentStock < warnNum) {
            item.warningLevel = '重要'
            item.warningStatus = '已预警'
          } else if (currentStock < safetyStock) {
            item.warningLevel = '一般'
            item.warningStatus = '已预警'
          } else {
            item.warningLevel = ''
            item.warningStatus = '正常'
          }
          // è®¡ç®—预计缺货时间(基于日均消耗量,这里简化处理)
          if (item.warningStatus === '已预警' && currentStock > 0 && warnNum > 0) {
            const dailyConsumption = warnNum / 30 // å‡è®¾30天消耗完最低库存
            const daysRemaining = Math.floor(currentStock / dailyConsumption)
            if (daysRemaining > 0) {
              const date = new Date()
              date.setDate(date.getDate() + daysRemaining)
              item.expectedShortageTime = date.toISOString().split('T')[0]
            }
          }
          item.currentStock = currentStock
          item.safetyStock = safetyStock
          return item
        })
      }
    })
    .catch(err => {
      tableLoading.value = false
      ElMessage.error(err.msg || '获取数据失败')
    })
}
// æœç´¢
const handleQuery = () => {
  page.current = 1
  getList()
}
// é‡ç½®æœç´¢
const resetQuery = () => {
  Object.keys(searchForm).forEach(key => {
    searchForm[key] = ''
  })
  handleQuery()
}
// åˆ†é¡µå˜åŒ–
const paginationChange = (obj) => {
  page.current = obj.page
  page.size = obj.limit
  getList()
}
// èŽ·å–åº“å­˜æ ·å¼ç±»
const getStockClass = (row) => {
  const currentStock = parseFloat(row.currentStock || row.inboundNum0 || 0)
  const warnNum = parseFloat(row.warnNum || 0)
  if (currentStock <= 0) {
    return 'text-danger'
  } else if (currentStock < warnNum) {
    return 'text-warning'
  }
  return 'text-success'
}
// èŽ·å–é¢„è­¦çº§åˆ«æ ‡ç­¾æ ·å¼
const getWarningLevelTag = (level) => {
  const levelMap = {
    '紧急': 'danger',
    '重要': 'warning',
    '一般': 'info'
  }
  return levelMap[level] || 'info'
}
// èŽ·å–å€’è®¡æ—¶ä¿¡æ¯
const getCountdown = (expectedTime) => {
  if (!expectedTime) return { text: '-', isExpired: false }
  const now = new Date().getTime()
  const expected = new Date(expectedTime).getTime()
  const diff = expected - now
  if (diff <= 0) {
    return { text: '已缺货', isExpired: true }
  }
  const days = Math.floor(diff / (1000 * 60 * 60 * 24))
  const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
  const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
  if (days > 0) {
    return { text: `${days}天${hours}小时`, isExpired: false }
  } else if (hours > 0) {
    return { text: `${hours}小时${minutes}分钟`, isExpired: false }
  } else {
    return { text: `${minutes}分钟`, isExpired: false }
  }
}
// èŽ·å–å€’è®¡æ—¶æ ·å¼ç±»
const getCountdownClass = (expectedTime) => {
  if (!expectedTime) return ''
  const now = new Date().getTime()
  const expected = new Date(expectedTime).getTime()
  const diff = expected - now
  if (diff <= 0) {
    return 'countdown-expired'
  } else if (diff <= 24 * 60 * 60 * 1000) { // 24小时内
    return 'countdown-urgent'
  } else if (diff <= 7 * 24 * 60 * 60 * 1000) { // 7天内
    return 'countdown-warning'
  } else {
    return 'countdown-normal'
  }
}
// é¡µé¢åŠ è½½
onMounted(() => {
  getList()
})
</script>
<style scoped lang="scss">
.app-container {
  padding: 20px;
  .search_form {
    background: #fff;
    padding: 20px;
    border-radius: 4px;
    margin-bottom: 20px;
  }
  .table_list {
    background: #fff;
    border-radius: 4px;
    padding: 20px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    .actions {
      display: flex;
      justify-content: space-between;
      margin-bottom: 20px;
    }
  }
  .text-danger {
    color: #f56c6c;
    font-weight: bold;
  }
  .text-warning {
    color: #e6a23c;
    font-weight: bold;
  }
  .text-success {
    color: #67c23a;
    font-weight: bold;
  }
  .countdown-timer {
    font-weight: bold;
  }
  .countdown-normal {
    color: #67c23a;
  }
  .countdown-warning {
    color: #e6a23c;
  }
  .countdown-urgent {
    color: #f56c6c;
    animation: blink 1s infinite;
  }
  .countdown-expired {
    color: #f56c6c;
    font-weight: bold;
  }
  @keyframes blink {
    0%, 50% { opacity: 1; }
    51%, 100% { opacity: 0.5; }
  }
}
</style>
src/views/monitor/job/index.vue
@@ -87,7 +87,7 @@
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table v-loading="loading" :data="jobList" @selection-change="handleSelectionChange">
      <el-table v-loading="loading" :data="jobList" @selection-change="handleSelectionChange" stripe>
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="任务编号" width="100" align="center" prop="jobId" />
         <el-table-column label="任务名称" align="center" prop="jobName" :show-overflow-tooltip="true" />
src/views/monitor/job/log.vue
@@ -96,7 +96,7 @@
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table v-loading="loading" :data="jobLogList" @selection-change="handleSelectionChange">
      <el-table v-loading="loading" :data="jobLogList" @selection-change="handleSelectionChange" stripe>
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="日志编号" width="80" align="center" prop="jobLogId" />
         <el-table-column label="任务名称" align="center" prop="jobName" :show-overflow-tooltip="true" />
src/views/monitor/logininfor/index.vue
@@ -93,7 +93,7 @@
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table ref="logininforRef" v-loading="loading" :data="logininforList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange">
      <el-table ref="logininforRef" v-loading="loading" :data="logininforList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange" stripe>
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="访问编号" align="center" prop="infoId" />
         <el-table-column label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']" />
src/views/monitor/online/index.vue
@@ -28,6 +28,7 @@
         v-loading="loading"
         :data="onlineList.slice((pageNum - 1) * pageSize, pageNum * pageSize)"
         style="width: 100%;"
         stripe
      >
         <el-table-column label="序号" width="50" type="index" align="center">
            <template #default="scope">
src/views/monitor/operlog/index.vue
@@ -107,7 +107,7 @@
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table ref="operlogRef" v-loading="loading" :data="operlogList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange">
      <el-table ref="operlogRef" v-loading="loading" :data="operlogList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange" stripe>
         <el-table-column type="selection" width="50" align="center" />
         <el-table-column label="日志编号" align="center" prop="operId" />
         <el-table-column label="系统模块" align="center" prop="title" :show-overflow-tooltip="true" />
src/views/personnelManagement/onboarding/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,348 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">姓名:</span>
        <el-input
          v-model="searchForm.staffName"
          style="width: 240px"
          placeholder="请输入姓名搜索"
          @change="handleQuery"
          clearable
          :prefix-icon="Search"
        />
        <span style="margin-left: 10px;"  class="search_title">合同开始日期:</span>
        <el-date-picker
            v-model="searchForm.entryDateStart"
            type="date"
            placeholder="请选择合同开始日期"
            size="default"
            @change="(date) => handleDateChange(date,1)"
        />
        <span style="margin-left: 10px;" class="search_title">合同结束日期:</span>
        <el-date-picker
            v-model="searchForm.entryDateEnd"
            type="date"
            placeholder="请选择合同结束日期"
            size="default"
            @change="(date) => handleDateChange(date,2)"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
          >搜索</el-button
        >
      </div>
      <div>
        <el-button type="primary" @click="openForm('add')">新增入职</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :isSelection="true"
        @selection-change="handleSelectionChange"
        :tableLoading="tableLoading"
        @pagination="pagination"
        :total="page.total"
      ></PIMTable>
    </div>
    <form-dia ref="formDia" @close="handleQuery"></form-dia>
  </div>
</template>
<script setup>
import { Search } from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import FormDia from "@/views/personnelManagement/onboarding/components/formDia.vue";
import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import {ElMessageBox} from "element-plus";
import dayjs from "dayjs";
const data = reactive({
  searchForm: {
    staffName: "",
  },
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
  {
    label: "状态",
    prop: "staffState",
    dataType: "tag",
    formatData: (params) => {
      if (params == 0) {
        return "离职";
      } else if (params == 1) {
        return "入职";
      } else {
        return null;
      }
    },
    formatType: (params) => {
      if (params == 0) {
        return "danger";
      } else if (params == 1) {
        return "primary";
      } else {
        return null;
      }
    },
  },
  {
    label: "员工编号",
    prop: "staffNo",
  },
  {
    label: "姓名",
    prop: "staffName",
  },
  {
    label: "性别",
    prop: "sex",
  },
  {
    label: "籍贯",
    prop: "nativePlace",
  },
  {
    label: "岗位",
    prop: "postJob",
  },
  {
    label: "家庭住址",
    prop: "adress",
    width:200
  },
  {
    label: "第一学历",
    prop: "firstStudy",
  },
  {
    label: "专业",
    prop: "profession",
    width:100
  },
  {
    label: "身份证号",
    prop: "identityCard",
    width:200
  },
  {
    label: "年龄",
    prop: "age",
  },
  {
    label: "联系电话",
    prop: "phone",
    width:150
  },
  {
    label: "紧急联系人",
    prop: "emergencyContact",
    width: 120
  },
  {
    label: "联系电话",
    prop: "emergencyContactPhone",
    width:150
  },
  {
    label: "试用期(月)",
    prop: "probationPeriod",
    width: 120,
  },
  // {
  //   label: "转正日期",
  //   prop: "probationEndDate",
  //   width: 120,
  //   formatData: (row) => {
  //     // ä¿®æ”¹ä¸ºä½¿ç”¨åˆåŒå¼€å§‹æ—¥æœŸè®¡ç®—转正日期
  //     if (row.contractStartTime && row.probationPeriod) {
  //       // è®¡ç®—转正日期(合同开始日期加上试用期月数)
  //       return dayjs(row.contractStartTime).add(row.probationPeriod, 'month').format('YYYY-MM-DD');
  //     }
  //     return '';
  //   },
  //   formatType: (row) => {
  //     // ä¿®æ”¹ä¸ºä½¿ç”¨åˆåŒå¼€å§‹æ—¥æœŸæ£€æŸ¥æ˜¯å¦ä¸´è¿‘转正(7天内)
  //     if (row.contractStartTime && row.probationPeriod) {
  //       const probationEndDate = dayjs(row.contractStartTime).add(row.probationPeriod, 'month');
  //       const daysUntilProbationEnd = probationEndDate.diff(dayjs(), 'day');
  //       if (daysUntilProbationEnd >= 0 && daysUntilProbationEnd <= 7) {
  //         return 'warning'; // ä½¿ç”¨è­¦å‘Šæ ·å¼æ ‡è®°ä¸´è¿‘转正的员工
  //       }
  //     }
  //     return '';
  //   }
  // },
  {
    label: "合同年限(年)",
    prop: "contractTerm",
    width: 120,
  },
  {
    label: "合同开始日期",
    prop: "contractStartTime",
    width: 120
  },
  {
    label: "合同结束日期",
    prop: "contractEndTime",
    width: 120
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: 'right',
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openForm("edit", row);
        },
      },
    ],
  },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
  total: 0,
});
const formDia = ref()
const { proxy } = getCurrentInstance()
const handleDateChange = (value,type) => {
  searchForm.value.entryDateEnd = null
  searchForm.value.entryDateStart = null
  if(type === 1){
    if (value) {
      searchForm.value.entryDateStart = dayjs(value).format("YYYY-MM-DD");
    }
  }else{
    if (value) {
      searchForm.value.entryDateEnd = dayjs(value).format("YYYY-MM-DD");
    }
  }
  getList();
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  staffJoinListPage({...page, ...searchForm.value, staffState: 1}).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records
    page.total = res.data.total;
    // æ£€æŸ¥æ˜¯å¦æœ‰ä¸´è¿‘转正的员工并提醒
    checkProbationEnding(tableData.value);
  }).catch(err => {
    tableLoading.value = false;
  })
};
// æ£€æŸ¥ä¸´è¿‘转正的员工并提醒
const checkProbationEnding = (data) => {
  const probationEndingSoon = [];
  data.forEach(item => {
    // ä¿®æ”¹ä¸ºä½¿ç”¨åˆåŒå¼€å§‹æ—¥æœŸæ£€æŸ¥
    if (item.contractStartTime && item.probationPeriod) {
      const probationEndDate = dayjs(item.contractStartTime).add(item.probationPeriod, 'month');
      const daysUntilProbationEnd = probationEndDate.diff(dayjs(), 'day');
      if (daysUntilProbationEnd >= 0 && daysUntilProbationEnd <= 7) {
        probationEndingSoon.push({
          staffName: item.staffName,
          probationEndDate: probationEndDate.format('YYYY-MM-DD'),
          daysLeft: daysUntilProbationEnd
        });
      }
    }
  });
  if (probationEndingSoon.length > 0) {
    let message = '以下员工将在7天内转正:\n';
    probationEndingSoon.forEach(item => {
      message += `${item.staffName}(${item.probationEndDate},还有${item.daysLeft}天)\n`;
    });
    // æ˜¾ç¤ºæé†’消息
    proxy.$modal.msgInfo(message);
  }
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æ‰“开弹框
const openForm = (type, row) => {
  nextTick(() => {
    formDia.value?.openDialog(type, row)
  })
};
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        staffJoinDel(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        proxy.download("/staff/staffJoinLeaveRecord/export", {staffState: 1}, "人员入职.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
onMounted(() => {
  getList();
});
</script>
<style scoped></style>
src/views/procurementManagement/paymentLedger/index.vue
@@ -29,6 +29,7 @@
            height="calc(100vh - 18.5em)"
            :highlight-current-row="true"
            style="width: 100%"
            stripe
            tooltip-effect="dark"
            @row-click="rowClick"
            :show-summary="isShowSummary"
src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue
@@ -23,7 +23,7 @@
      </el-col>
      <el-col :span="12">
        <el-form-item label="发票号:">
          <el-input disabled v-model="form.invoiceNumber" />
                    <el-input v-model="form.invoiceNumber" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
@@ -53,7 +53,10 @@
defineOptions({
  name: "来票台账表单",
});
const temFutureTickets = ref(0)
const temFutureTickets = ref(0) // åˆå§‹æœªæ¥ç¥¨æ•°
const initialTicketsNum = ref(0) // åˆå§‹æ¥ç¥¨æ•°
const initialTicketsAmount = ref(0) // åˆå§‹æ¥ç¥¨é‡‘额
const quantity = ref(0) // æ€»æ•°é‡
const { form, resetForm } = useFormData({
  id: undefined,
  purchaseContractNumber: undefined, // é‡‡è´­åˆåŒå·
@@ -63,6 +66,7 @@
  ticketsNum: undefined, // æ¥ç¥¨æ•°
  ticketsAmount: undefined, // æ¥ç¥¨é‡‘额
    taxInclusiveUnitPrice: undefined, // å«ç¨Žå•ä»·
    ticketRegistrationId: undefined, // å«ç¨Žå•ä»·
});
const load = async (id) => {
@@ -74,10 +78,15 @@
    form.createdAt = data.createdAt;
    form.invoiceNumber = data.invoiceNumber;
    form.ticketsNum = data.ticketsNum;
    form.ticketsAmount = data.ticketsAmount.toFixed(2);
        form.ticketsAmount = data.ticketsAmount ? Number(data.ticketsAmount).toFixed(2) : 0;
    form.taxInclusiveUnitPrice = data.taxInclusiveUnitPrice;
    form.futureTickets = data.futureTickets;
    temFutureTickets.value = data.futureTickets;
        initialTicketsNum.value = data.ticketsNum || 0;
        initialTicketsAmount.value = data.ticketsAmount || 0;
        form.ticketRegistrationId = data.ticketRegistrationId;
        // èŽ·å–æ€»æ•°é‡ï¼Œå¦‚æžœæ•°æ®ä¸­æœ‰ quantity å­—段则使用,否则使用来票数+未来票数
        quantity.value = data.quantity || (Number(data.ticketsNum || 0) + Number(data.futureTickets || 0));
  }
};
@@ -87,15 +96,44 @@
        proxy.$modal.msgWarning("含税单价不能为零或未定义");
        return;
    }
    if (Number(form.ticketsNum) > Number(temFutureTickets.value)) {
        proxy.$modal.msgWarning("开票数不得大于未开票数");
        form.ticketsNum = temFutureTickets.value
    const newTicketsNum = Number(form.ticketsNum) || 0;
    const currentTicketsNum = Number(initialTicketsNum.value) || 0;
    // è®¡ç®—新增的来票数
    const addedTicketsNum = newTicketsNum - currentTicketsNum;
    // è®¡ç®—新的未来票数 = åˆå§‹æœªæ¥ç¥¨æ•° - æ–°å¢žçš„æ¥ç¥¨æ•°
    const newFutureTickets = Number(temFutureTickets.value) - addedTicketsNum;
    // éªŒè¯ï¼šæ–°çš„æ¥ç¥¨æ•° + æ–°çš„æœªæ¥ç¥¨æ•° â‰¤ quantity
    if (newTicketsNum + newFutureTickets > Number(quantity.value)) {
        proxy.$modal.msgWarning(`来票数+未来票数不能大于总数量(${quantity.value})`);
        // é™åˆ¶æ¥ç¥¨æ•°ï¼Œä½¿å…¶æ»¡è¶³ï¼šæ¥ç¥¨æ•° + æœªæ¥ç¥¨æ•° â‰¤ quantity
        // æœ€å¤§æ¥ç¥¨æ•° = quantity - åˆå§‹æœªæ¥ç¥¨æ•° + åˆå§‹æ¥ç¥¨æ•°
        const maxTicketsNum = Number(quantity.value) - Number(temFutureTickets.value) + Number(initialTicketsNum.value);
        form.ticketsNum = Math.max(0, Math.min(maxTicketsNum, newTicketsNum));
        // é‡æ–°è®¡ç®—
        const recalculatedAddedTicketsNum = Number(form.ticketsNum) - Number(initialTicketsNum.value);
        const recalculatedFutureTickets = Number(temFutureTickets.value) - recalculatedAddedTicketsNum;
        form.futureTickets = Number(recalculatedFutureTickets.toFixed(2));
        const ticketsAmount = Number(form.ticketsNum) * Number(form.taxInclusiveUnitPrice);
        form.ticketsAmount = Number(ticketsAmount.toFixed(2));
        return;
    }
    // æ£€æŸ¥æ–°å¢žçš„æ¥ç¥¨æ•°æ˜¯å¦å¤§äºŽåˆå§‹æœªæ¥ç¥¨æ•°
    if (addedTicketsNum > Number(temFutureTickets.value)) {
        proxy.$modal.msgWarning("新增开票数不得大于未开票数");
        form.ticketsNum = Number(initialTicketsNum.value) + Number(temFutureTickets.value);
    }
    
    // ç¡®ä¿æ‰€æœ‰æ•°å€¼éƒ½è½¬æ¢ä¸ºæ•°å­—类型进行计算
    const ticketsAmount = Number(form.ticketsNum) * Number(form.taxInclusiveUnitPrice);
    const futureTickets = Number(temFutureTickets.value) - Number(form.ticketsNum);
    form.futureTickets = Number(futureTickets.toFixed(2));
    const finalTicketsNum = Number(form.ticketsNum) || 0;
    const finalAddedTicketsNum = finalTicketsNum - Number(initialTicketsNum.value);
    const finalFutureTickets = Number(temFutureTickets.value) - finalAddedTicketsNum;
    const ticketsAmount = finalTicketsNum * Number(form.taxInclusiveUnitPrice);
    form.futureTickets = Number(finalFutureTickets.toFixed(2));
    form.ticketsAmount = Number(ticketsAmount.toFixed(2));
};
const inputTicketsAmount = (val) => {
@@ -105,17 +143,47 @@
        return;
    }
    
    if (Number(val) > Number(form.futureTickets*form.taxInclusiveUnitPrice)) {
        proxy.$modal.msgWarning("本次来票金额不得大于总金额");
        form.ticketsAmount = (form.futureTickets*form.taxInclusiveUnitPrice).toFixed(2)
        const ticketsNum = Number(form.ticketsAmount) / Number(form.taxInclusiveUnitPrice);
        form.ticketsNum = Number(ticketsNum.toFixed(2))
    const newTicketsAmount = Number(val) || 0;
    // è®¡ç®—新的来票数
    const newTicketsNum = newTicketsAmount / Number(form.taxInclusiveUnitPrice);
    const currentTicketsNum = Number(initialTicketsNum.value) || 0;
    // è®¡ç®—新增的来票数
    const addedTicketsNum = newTicketsNum - currentTicketsNum;
    // è®¡ç®—新的未来票数 = åˆå§‹æœªæ¥ç¥¨æ•° - æ–°å¢žçš„æ¥ç¥¨æ•°
    const newFutureTickets = Number(temFutureTickets.value) - addedTicketsNum;
    // éªŒè¯ï¼šæ–°çš„æ¥ç¥¨æ•° + æ–°çš„æœªæ¥ç¥¨æ•° â‰¤ quantity
    if (newTicketsNum + newFutureTickets > Number(quantity.value)) {
        proxy.$modal.msgWarning(`来票数+未来票数不能大于总数量(${quantity.value})`);
        // é™åˆ¶æ¥ç¥¨æ•°ï¼Œä½¿å…¶æ»¡è¶³ï¼šæ¥ç¥¨æ•° + æœªæ¥ç¥¨æ•° â‰¤ quantity
        const maxTicketsNum = Number(quantity.value) - Number(temFutureTickets.value) + Number(initialTicketsNum.value);
        form.ticketsNum = Math.max(0, Math.min(maxTicketsNum, newTicketsNum));
        form.ticketsAmount = Number((form.ticketsNum * Number(form.taxInclusiveUnitPrice)).toFixed(2));
        const recalculatedAddedTicketsNum = Number(form.ticketsNum) - Number(initialTicketsNum.value);
        const recalculatedFutureTickets = Number(temFutureTickets.value) - recalculatedAddedTicketsNum;
        form.futureTickets = Number(recalculatedFutureTickets.toFixed(2));
        return;
    }
    // æ£€æŸ¥æ–°å¢žçš„æ¥ç¥¨é‡‘额是否大于初始未来票数对应的金额
    const maxAddedAmount = Number(temFutureTickets.value * form.taxInclusiveUnitPrice);
    if (addedTicketsNum > 0 && addedTicketsNum * Number(form.taxInclusiveUnitPrice) > maxAddedAmount) {
        proxy.$modal.msgWarning("新增来票金额不得大于未开票金额");
        form.ticketsAmount = Number((initialTicketsAmount.value + maxAddedAmount).toFixed(2));
        form.ticketsNum = Number((currentTicketsNum + Number(temFutureTickets.value)).toFixed(2));
        form.futureTickets = 0;
        return;
    }
    
    // ç¡®ä¿æ‰€æœ‰æ•°å€¼éƒ½è½¬æ¢ä¸ºæ•°å­—类型进行计算
    const ticketsNum = Number(val) / Number(form.taxInclusiveUnitPrice);
    form.ticketsNum = Number(ticketsNum.toFixed(2));
    const finalTicketsNum = Number(newTicketsNum.toFixed(2));
    const finalAddedTicketsNum = finalTicketsNum - Number(initialTicketsNum.value);
    const finalFutureTickets = Number(temFutureTickets.value) - finalAddedTicketsNum;
    form.ticketsNum = finalTicketsNum;
    form.futureTickets = Number(finalFutureTickets.toFixed(2));
};
defineExpose({
src/views/procurementManagement/procurementInvoiceLedger/indexOld.vue
@@ -52,6 +52,7 @@
        :summary-method="summarizeMainTable"
        @expand-change="expandChange"
        height="calc(100vh - 18.5em)"
        stripe
      >
        <el-table-column align="center" label="序号" type="index" width="55" />
        <el-table-column type="expand">
@@ -61,6 +62,7 @@
              border
              show-summary
              :summary-method="summarizeChildrenTable"
              stripe
            >
              <el-table-column
                align="center"
src/views/productionManagement/operationScheduling/components/formDia.vue
src/views/productionManagement/productionDispatching/components/formDia.vue
@@ -37,6 +37,9 @@
              <el-input v-model="form.quantity" placeholder="请输入" clearable disabled/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
                        <el-form-item label="待排产数量:" prop="pendingQuantity">
                            <el-input v-model="form.pendingQuantity" placeholder="请输入" clearable disabled/>
@@ -111,6 +114,7 @@
<script setup>
import {ref} from "vue";
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {productionDispatch} from "@/api/productionManagement/productionOrder.js";
import useUserStore from "@/store/modules/user.js";
@@ -131,6 +135,7 @@
        schedulingDate: "",
        pendingQuantity: "",
        speculativeTradingName: "", // ç»‘定机器名称
        salesLedgerProductId: "",
  },
  rules: {
        schedulingNum: [{ required: true, message: "请输入", trigger: "blur" },],
@@ -151,6 +156,10 @@
        userList.value = res.data;
    });
    form.value = {...row}
    // ç»‘定外层传入的产品ID到后端需要的 salesLedgerProductId å­—段
    form.value.salesLedgerProductId = row.id;
    // ç¡®ä¿ä¸ä¼šæŠŠåŽŸå§‹ id å½“作排产记录主键传给后端
    delete form.value.id;
    form.value.schedulingNum = 0
    form.value.schedulingUserId = userStore.id
    form.value.schedulingDate = dayjs().format("YYYY-MM-DD");
src/views/productionManagement/productionReporting/index.vue
src/views/productionManagement/safetyMonitoring/index.vue
@@ -173,7 +173,7 @@
            :visible.sync="emergencyRecordsVisible"
            width="800px"
        >
            <el-table :data="emergencyRecords" style="width: 100%">
            <el-table :data="emergencyRecords" style="width: 100%" stripe>
                <el-table-column prop="time" label="时间" width="180"></el-table-column>
                <el-table-column prop="location" label="位置" width="150"></el-table-column>
                <el-table-column prop="type" label="类型" width="120"></el-table-column>
@@ -211,7 +211,7 @@
                
                <div class="sensor-data-section">
                    <h4>传感器数据</h4>
                    <el-table :data="currentEvent.sensorData" style="width: 100%">
                    <el-table :data="currentEvent.sensorData" style="width: 100%" stripe>
                        <el-table-column prop="sensor" label="传感器"></el-table-column>
                        <el-table-column prop="methane" label="甲烷浓度"></el-table-column>
                        <el-table-column prop="h2s" label="硫化氢浓度"></el-table-column>
src/views/salesManagement/invoiceLedger/index.vue
@@ -27,7 +27,7 @@
    </div>
    <div class="table_list">
      <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
        :row-key="(row) => row.id" show-summary :summary-method="summarizeMainTable" height="calc(100vh - 18.5em)">
        :row-key="(row) => row.id" show-summary :summary-method="summarizeMainTable" height="calc(100vh - 18.5em)" stripe>
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="销售合同号" prop="salesContractNo" show-overflow-tooltip width="180" />
@@ -294,8 +294,8 @@
// ä¸Šä¼ å‰æ ¡æ£€
function handleBeforeUpload(file) {
  // æ ¡æ£€æ–‡ä»¶å¤§å°
  if (file.size > 1024 * 1024 * 10) {
    proxy.$modal.msgError("上传文件大小不能超过10MB!");
  if (file.size > 1024 * 1024 * 50) {
    proxy.$modal.msgError("上传文件大小不能超过50MB!");
    return false;
  }
  // åˆ¤æ–­æ–‡ä»¶æ ¼å¼æ˜¯å¦ç¬¦åˆ
src/views/salesManagement/receiptPaymentHistory/index.vue
@@ -101,19 +101,6 @@
  {
    label: "回款方式",
    prop: "receiptPaymentType",
    dataType: "tag",
    formatData: (params) => {
      if (params == 0) {
        return "电汇";
      } else if (params == 1) {
        return "承兑";
      } else {
        return null;
      }
    },
    formatType: (params) => {
      return "info";
    },
  },
  {
    label: "登记人",
src/views/salesManagement/receiptPaymentLedger/index.vue
@@ -27,6 +27,7 @@
          :summary-method="summarizeMainTable"
          @row-click="rowClickMethod"
          height="calc(100vh - 18.5em)"
          stripe
        >
          <el-table-column
            align="center"
@@ -81,6 +82,7 @@
          :data="receiptRecord"
          border
          :row-key="(row) => row.id"
          stripe
          show-summary
          :summary-method="summarizeMainTable1"
          height="calc(100vh - 18.5em)"
src/views/salesManagement/salesLedger/fileList.vue
@@ -1,6 +1,6 @@
<template>
  <el-dialog v-model="dialogVisible" title="附件" width="40%" :before-close="handleClose">
    <el-table :data="tableData" border height="40vh">
    <el-table :data="tableData" border height="40vh" stripe>
      <el-table-column label="附件名称" prop="name" min-width="400" show-overflow-tooltip />
      <el-table-column fixed="right" label="操作" width="100" align="center">
        <template #default="scope">
src/views/salesManagement/salesLedger/index.vue
@@ -41,11 +41,11 @@
      </div>
      <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
        :expand-row-keys="expandedRowKeys" :row-key="(row) => row.id" show-summary style="width: 100%"
        :summary-method="summarizeMainTable" @expand-change="expandChange" height="calc(100vh - 18.5em)">
        :summary-method="summarizeMainTable" @expand-change="expandChange" height="calc(100vh - 18.5em)" stripe>
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column type="expand">
          <template #default="props">
            <el-table :data="props.row.children" border show-summary :summary-method="summarizeChildrenTable">
            <el-table :data="props.row.children" border show-summary :summary-method="summarizeChildrenTable" stripe>
              <el-table-column align="center" label="序号" type="index" width="60" />
              <el-table-column label="产品大类" prop="productCategory" />
              <el-table-column label="规格型号" prop="specificationModel" />
@@ -98,6 +98,14 @@
        <el-table-column label="付款方式" prop="paymentMethod" show-overflow-tooltip />
        <el-table-column label="合同金额(元)" prop="contractAmount" width="220" show-overflow-tooltip
          :formatter="formattedNumber" />
        <el-table-column label="已开票金额(元)" prop="invoiceTotal" width="220" show-overflow-tooltip
                         :formatter="formattedNumber" />
        <el-table-column label="未开票金额(元)" prop="noInvoiceAmountTotal" width="220" show-overflow-tooltip
                         :formatter="formattedNumber" />
        <el-table-column label="回款金额(元)" prop="receiptPaymentAmountTotal" width="220" show-overflow-tooltip
                         :formatter="formattedNumber" />
        <el-table-column label="待回款金额(元)" prop="noReceiptAmount" width="220" show-overflow-tooltip
                         :formatter="formattedNumber" />
        <el-table-column label="录入人" prop="entryPersonName" width="100" show-overflow-tooltip />
        <el-table-column label="录入日期" prop="entryDate" width="120" show-overflow-tooltip />
        <el-table-column label="签订日期" prop="executionDate" width="120" show-overflow-tooltip />
@@ -124,10 +132,12 @@
          </el-col>
          <el-col :span="12">
            <el-form-item label="业务员:" prop="salesman">
              <el-select v-model="form.salesman" placeholder="请选择" clearable :disabled="operationType === 'view'">
                <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
                  :value="item.nickName" />
              </el-select>
              <el-input
                v-model="form.salesman"
                placeholder="请输入"
                clearable
                :disabled="operationType === 'view'"
              />
            </el-form-item>
          </el-col>
        </el-row>
@@ -190,7 +200,7 @@
            <el-button v-if="operationType !== 'view'" plain type="danger" @click="deleteProduct" >删除</el-button>
          </el-form-item>
        </el-row>
        <el-table :data="productData" border @selection-change="productSelected" show-summary
        <el-table :data="productData" border @selection-change="productSelected" show-summary stripe
          :summary-method="summarizeMainTable">
          <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'" />
          <el-table-column align="center" label="序号" type="index" width="60" />
@@ -874,6 +884,10 @@
    "contractAmount",
    "taxInclusiveTotalPrice",
    "taxExclusiveTotalPrice",
        'invoiceTotal',
        'noInvoiceAmountTotal',
        'receiptPaymentAmountTotal',
        'noReceiptAmount',
  ]);
};
// å­è¡¨åˆè®¡æ–¹æ³•
src/views/system/config/index.vue
@@ -96,7 +96,7 @@
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange">
      <el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange" stripe>
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="参数主键" align="center" prop="configId" />
         <el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" />
src/views/system/dict/data.vue
@@ -86,7 +86,7 @@
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
      <el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange" stripe>
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="字典编码" align="center" prop="dictCode" />
         <el-table-column label="字典标签" align="center" prop="dictLabel">
src/views/system/dict/index.vue
@@ -101,7 +101,7 @@
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table v-loading="loading" :data="typeList" @selection-change="handleSelectionChange">
      <el-table v-loading="loading" :data="typeList" @selection-change="handleSelectionChange" stripe>
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="字典编号" align="center" prop="dictId" />
         <el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true"/>
src/views/system/menu/index.vue
@@ -51,6 +51,7 @@
         v-if="refreshTable"
         v-loading="loading"
         :data="menuList"
         stripe
         row-key="menuId"
         :default-expand-all="isExpandAll"
         :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
src/views/system/notice/index.vue
@@ -68,7 +68,7 @@
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table v-loading="loading" :data="noticeList" @selection-change="handleSelectionChange">
      <el-table v-loading="loading" :data="noticeList" @selection-change="handleSelectionChange" stripe>
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="序号" align="center" prop="noticeId" width="100" />
         <el-table-column
src/views/system/post/index.vue
@@ -77,7 +77,7 @@
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table v-loading="loading" :data="postList" @selection-change="handleSelectionChange">
      <el-table v-loading="loading" :data="postList" @selection-change="handleSelectionChange" stripe>
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="岗位编号" align="center" prop="postId" />
         <el-table-column label="岗位编码" align="center" prop="postCode" />
src/views/system/role/authUser.vue
@@ -57,7 +57,7 @@
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
      <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange" stripe>
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
         <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
src/views/system/role/index.vue
@@ -92,7 +92,7 @@
      </el-row>
      <!-- è¡¨æ ¼æ•°æ® -->
      <el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
      <el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange" stripe>
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="角色编号" prop="roleId" width="120" />
         <el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
src/views/system/role/selectUser.vue
@@ -26,7 +26,7 @@
         </el-form-item>
      </el-form>
      <el-row>
         <el-table @row-click="clickRow" ref="refTable" :data="userList" @selection-change="handleSelectionChange" height="260px">
         <el-table @row-click="clickRow" ref="refTable" :data="userList" @selection-change="handleSelectionChange" height="260px" stripe>
            <el-table-column type="selection" width="55"></el-table-column>
            <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
            <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
src/views/system/user/authRole.vue
@@ -17,7 +17,7 @@
      </el-form>
      <h4 class="form-header h4">角色信息</h4>
      <el-table v-loading="loading" :row-key="getRowKey" @row-click="clickRow" ref="roleRef" @selection-change="handleSelectionChange" :data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)">
      <el-table v-loading="loading" :row-key="getRowKey" @row-click="clickRow" ref="roleRef" @selection-change="handleSelectionChange" :data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)" stripe>
         <el-table-column label="序号" width="55" type="index" align="center">
            <template #default="scope">
               <span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
src/views/tideLogin.vue
@@ -1,15 +1,23 @@
<template>
  <div></div>
</template>
<script setup>
<script>
import useUserStore from '@/store/modules/user'
const userStore = useUserStore()
let { proxy } = getCurrentInstance()
function goLogin() {
  userStore.TideLogin({code : proxy.$route.query.code}).then(() => {
    proxy.$router.push({ path: redirect || "/" }).catch(() => { });
export default {
  data() {
    return {}
  },
  created() {
    this.goLogin()
  },
  computed: {},
  methods: {
     goLogin() {
       useUserStore().TideLogin({code : this.$route.query.code}).then(() => {
        this.$router.push({ path: this.redirect || "/" }).catch(() => { });
  })
}
goLogin()
  }
}
</script>
<style scoped></style>
src/views/tool/gen/editTable.vue
@@ -5,7 +5,7 @@
        <basic-info-form ref="basicInfo" :info="info" />
      </el-tab-pane>
      <el-tab-pane label="字段信息" name="columnInfo">
        <el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight">
        <el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight" stripe>
          <el-table-column label="序号" type="index" min-width="5%"/>
          <el-table-column
            label="字段列名"
src/views/tool/gen/importTable.vue
@@ -26,7 +26,7 @@
      </el-form-item>
    </el-form>
    <el-row>
      <el-table @row-click="clickRow" ref="table" :data="dbTableList" @selection-change="handleSelectionChange" height="260px">
      <el-table @row-click="clickRow" ref="table" :data="dbTableList" @selection-change="handleSelectionChange" height="260px" stripe>
        <el-table-column type="selection" width="55"></el-table-column>
        <el-table-column prop="tableName" label="表名称" :show-overflow-tooltip="true"></el-table-column>
        <el-table-column prop="tableComment" label="表描述" :show-overflow-tooltip="true"></el-table-column>
src/views/tool/gen/index.vue
@@ -106,6 +106,7 @@
      ref="genRef"
      v-loading="loading"
      :data="tableList"
      stripe
      @selection-change="handleSelectionChange"
      :default-sort="defaultSort"
      @sort-change="handleSortChange"
vite.config.js
@@ -7,17 +7,11 @@
  const env = loadEnv(mode, process.cwd());
  const { VITE_APP_ENV } = env;
  const baseUrl =
      env.VITE_APP_ENV === "development"
          ? "http://1.15.17.182:9003"
          : env.VITE_BASE_API;
  const javaUrl =
      env.VITE_APP_ENV === "development"
          ? "http://1.15.17.182:9002"
          : env.VITE_JAVA_API;
    VITE_APP_ENV == "development"
      ? "http://114.132.189.42:8089" // å¼€å‘环境后端接口
      : "http://114.132.189.42:7003"; // ç”Ÿäº§çŽ¯å¢ƒåŽç«¯æŽ¥å£
  return {
    define:{
      __BASE_API__: JSON.stringify(javaUrl)
    },
    // éƒ¨ç½²ç”Ÿäº§çŽ¯å¢ƒå’Œå¼€å‘çŽ¯å¢ƒä¸‹çš„URL。
    // é»˜è®¤æƒ…况下,vite ä¼šå‡è®¾ä½ çš„应用是被部署在一个域名的根路径上
    // ä¾‹å¦‚ https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl ä¸º /admin/。