maven
2025-09-22 52242e82b54965f3cf48ca06de14a784ada6087e
Merge remote-tracking branch 'origin/dev' into dev
已添加10个文件
已修改9个文件
4063 ■■■■■ 文件已修改
.env.development 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.production 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.staging 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
multiple/config.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/enterpriseBook.js 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/defectManagement.js 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/spareParts.js 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockReport.js 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/Logo.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/enterpriseBook/index.vue 802 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/knowledgeBase/index.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue 746 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/sealManagement/index.vue 74 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/defectManagement/index.vue 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/operationManagement/index.vue 824 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/spareParts/index.vue 417 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/index.vue 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockReport/index.vue 678 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.development
@@ -1,8 +1,8 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = æµ‹è¯•进销存管理系统
VITE_APP_TITLE = ä¸­å°ä¼ä¸šæ•°å­—化转型二级套餐包
# å¼€å‘环境配置
VITE_APP_ENV = 'development'
# æµ‹è¯•进销存管理系统/开发环境
# ä¸­å°ä¼ä¸šæ•°å­—化转型二级套餐包/开发环境
VITE_APP_BASE_API = '/dev-api'
.env.production
@@ -1,10 +1,10 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = æµ‹è¯•进销存管理系统
VITE_APP_TITLE = ä¸­å°ä¼ä¸šæ•°å­—化转型二级套餐包
# ç”Ÿäº§çŽ¯å¢ƒé…ç½®
VITE_APP_ENV = 'production'
# æµ‹è¯•进销存管理系统/生产环境
# ä¸­å°ä¼ä¸šæ•°å­—化转型二级套餐包/生产环境
VITE_APP_BASE_API = '/prod-api'
# æ˜¯å¦åœ¨æ‰“包时开启压缩,支持 gzip å’Œ brotli
.env.staging
@@ -1,10 +1,10 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = æµ‹è¯•进销存管理系统
VITE_APP_TITLE = ä¸­å°ä¼ä¸šæ•°å­—化转型二级套餐包
# ç”Ÿäº§çŽ¯å¢ƒé…ç½®
VITE_APP_ENV = 'staging'
# æµ‹è¯•进销存管理系统/生产环境
# ä¸­å°ä¼ä¸šæ•°å­—化转型二级套餐包/生产环境
VITE_APP_BASE_API = '/stage-api'
# æ˜¯å¦åœ¨æ‰“包时开启压缩,支持 gzip å’Œ brotli
multiple/config.json
@@ -15,7 +15,7 @@
    },
    "screen": "screen/HYSNView.png",
    "logo": "logo/LCLogo.png",
    "favicon": "favicon/BHMY.ico"
    "favicon": "favicon/favicon.ico"
  },
  "WDSY": {
    "env": {
src/api/collaborativeApproval/enterpriseBook.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,67 @@
import request from '@/utils/request'
// æŸ¥è¯¢ä¸ªäººé€šè®¯å½•
// ä¸ªäººé€šè®¯å½•通常是用户收藏或频繁联系的人员
export function getPersonalContacts(page,query) {
  return request({
    url: '/staffContactsPersonal/getList',
    method: 'get',
    params: {
      ...page,
      ...query
    }
  })
}
// æ·»åŠ è”ç³»äººåˆ°ä¸ªäººé€šè®¯å½•
export function addPersonalContact(data) {
  return request({
    url: '/staffContactsPersonal/add',
    method: 'post',
    data: data
  })
}
// ä»Žä¸ªäººé€šè®¯å½•移除联系人
export function removePersonalContact(id) {
  return request({
    url: '/staffContactsPersonal/delete/' + id,
    method: 'delete'
  })
}
// æŸ¥è¯¢å…¬å…±é€šè®¯å½•
// å…¬å…±é€šè®¯å½•通常是所有员工可见的联系方式
export function getPublicContacts(query) {
  return request({
    url: '/staff/contacts/public/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢å•位通讯录
// å•位通讯录通常按部门组织的员工联系方式
export function getCompanyContacts(query) {
  return request({
    url: '/staff/contacts/company/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢éƒ¨é—¨é€šè®¯å½•树结构
export function getDepartmentTree() {
  return request({
    url: '/staff/contacts/department/tree',
    method: 'get'
  })
}
// èŽ·å–å‘˜å·¥è¯¦ç»†ä¿¡æ¯
export function getEmployeeDetail(employeeId) {
  return request({
    url: '/staff/staffOnJob/' + employeeId,
    method: 'get'
  })
}
src/api/equipmentManagement/defectManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
import request from '@/utils/request';
// ç™»è®°ç¼ºé™·
export function registerDefect(data) {
  return request({
    url: '/defect/add',
    method: 'post',
    data
  });
}
// èŽ·å–ç¼ºé™·åˆ—è¡¨
export function getDefectList() {
  return request({
    url: '/defect/page',
    method: 'get'
  });
}
// æ¶ˆé™¤ç¼ºé™·-修改状态
export function eliminateDefect(data) {
  return request({
    url: '/defect/update',
    method: 'post',
    data
  });
}
//删除
export function deleteDefect(id) {
  return request({
    url: '/defect/delete',
    method: 'delete',
    id
  });
}
// èŽ·å–ç¼ºé™·è®¾å¤‡å°è´¦
export function getDefectLedger(deviceLedgerId) {
  return request({
    url: '/defect//find/' + deviceLedgerId,
    method: 'get'
  });
}
src/api/equipmentManagement/spareParts.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
import request from "@/utils/request";
/**
 *  å¤‡ä»¶åˆ†ç±»-树列表
 */
export const getSparePartsTree = (params) => {
  return request({
    url: "/spareParts/getTree",
    method: "get",
    params,
  });
};
/**
 *  å¤‡ä»¶åˆ†ç±»-分页查询列表
 */
export const getSparePartsList = (params) => {
  return request({
    url: "/spareParts/listPage",
    method: "get",
    params,
  });
};
/**
 * @desc æ–°å¢ž
 */
export const addSparePart = (data) => {
  return request({
    url: "/spareParts/add",
    method: "post",
    data,
  });
};
/**
 * @desc ç¼–辑
 */
export const editSparePart = (data) => {
  return request({
    url: "/spareParts/update",
    method: "post",
    data,
  });
};
/**
 * @desc åˆ é™¤æŠ¥ä¿®
 * @param {编号} ids
 * @returns
 */
export const delSparePart = (id) => {
  return request({
    url: '/spareParts/delete/'+id,
    method: "delete",
  });
};
src/api/inventoryManagement/stockReport.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
import request from "@/utils/request";
// èŽ·å–åº“å­˜æ—¥æŠ¥ç»Ÿè®¡
export const getStockDailyReport = (params) => {
    return request({
        url: "/stockreport/daily",
        method: "get",
        params,
    });
};
// èŽ·å–åº“å­˜æœˆæŠ¥ç»Ÿè®¡
export const getStockMonthlyReport = (params) => {
    return request({
        url: "/stockreport/monthly",
        method: "get",
        params,
    });
};
// èŽ·å–ä½œä¸šæŠ¥è¡¨ç»Ÿè®¡
export const getWorkReport = (params) => {
    return request({
        url: "/stockreport/work",
        method: "get",
        params,
    });
};
// èŽ·å–åº“å­˜è¿›å‡ºå­˜ç»Ÿè®¡
export const getStockInOutReport = (params) => {
    return request({
        url: "/stockreport/inout",
        method: "get",
        params,
    });
};
// å¯¼å‡ºåº“存报表
export const exportStockReport = (params) => {
    return request({
        url: "/stockreport/export",
        method: "get",
        params,
        responseType: 'blob'
    });
};
// èŽ·å–åº“å­˜è¶‹åŠ¿æ•°æ®
export const getStockTrendData = (params) => {
    return request({
        url: "/stockreport/trend",
        method: "get",
        params,
    });
};
src/layout/components/Sidebar/Logo.vue
@@ -3,11 +3,11 @@
    <transition name="sidebarLogoFade">
      <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
        <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="公司Logo" />
        <h1 v-else class="sidebar-title">{{ title }}</h1>
        <h1 class="sidebar-title">{{ title }}</h1>
      </router-link>
      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
        <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="公司Logo" />
        <h1 v-else class="sidebar-title">{{ title }}</h1>
        <h1 class="sidebar-title">{{ title }}</h1>
      </router-link>
    </transition>
  </div>
src/router/index.js
@@ -72,20 +72,6 @@
    ],
  },
  {
    path: "/main/MobileChat",
    component: Layout,
    redirect: "",
    hidden: true,
    children: [
      {
        path: "",
        component: () => import("@/views/chatHome/chatHomeIndex/MobileChat"),
        name: "MobileChat",
        meta: { title: "AI对话", icon: "dashboard", affix: true },
      },
    ],
  },
  {
    path: "/user",
    component: Layout,
    hidden: true,
src/views/collaborativeApproval/enterpriseBook/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,802 @@
<template>
  <div class="app-container">
    <!-- å¤´éƒ¨å¯¼èˆª -->
    <!-- <div class="header">
      <h2>企业通讯录管理</h2>
      <p>管理个人、公共和单位的联系方式</p>
    </div> -->
    <!-- æ ‡ç­¾é¡µåˆ‡æ¢ -->
    <el-tabs v-model="activeTab" @tab-change="handleTabChange" type="border-card">
      <el-tab-pane label="个人通讯录" name="personal">
        <div class="tab-content">
          <!-- æœç´¢æ¡† -->
          <el-input
            v-model="personalSearch.staffName"
            placeholder="搜索联系人"
            clearable
            prefix-icon="Search"
            class="search-input"
            @keyup.enter="getPersonalContactsList"
          />
          <el-button style="margin: 0 0 20px 20px;" type="primary" @click="showAddContactDialog=true">添加联系人</el-button>
          <!-- è”系人列表 -->
          <div class="contact-list">
            <div
              v-for="contact in personalContacts"
              :key="contact.id"
              class="contact-card"
              @click="showContactDetail(contact)"
            >
              <div class="contact-avatar">{{ contact.staffName.charAt(0) }}</div>
              <div class="contact-info">
                <h4>{{ contact.staffName }}</h4>
                <p>{{ contact.profession }} - {{ contact.postJob }}</p>
                <div class="contact-phone">{{ contact.phone }}</div>
              </div>
              <div class="contact-actions">
                <!-- <el-button
                  type="text"
                  icon="Phone"
                  @click.stop="callContact(contact)"
                ></el-button> -->
                <el-button
                  type="text"
                  icon="Message"
                  @click.stop="messageContact(contact)"
                ></el-button>
                <el-button
                  type="text"
                  icon="Delete"
                  @click.stop="removeFromPersonalContacts(contact.id)"
                ></el-button>
              </div>
            </div>
            <!-- ç©ºçŠ¶æ€
            <div v-if="personalContacts.length === 0 && !loading" class="empty-state">
              <el-empty description="暂无联系人" />
              <el-button type="primary" @click="showAddContactDialog=true">添加联系人</el-button>
            </div> -->
          </div>
        </div>
      </el-tab-pane>
      <el-tab-pane label="公共通讯录" name="public">
        <div class="tab-content">
          <!-- æœç´¢æ¡† -->
          <el-input
            v-model="publicSearch.staffName"
            placeholder="搜索公共联系人"
            clearable
            prefix-icon="Search"
            class="search-input"
            @keyup.enter="getPublicContactsList"
          />
          <!-- è”系人列表 publicContacts-->
          <div class="contact-list">
            <div
              v-for="contact in EmployeeList"
              :key="contact.id"
              class="contact-card"
              @click="showContactDetail(contact)"
            >
              <div class="contact-avatar">{{ contact.staffName.charAt(0) }}</div>
              <div class="contact-info">
                <h4>{{ contact.staffName }}</h4>
                <p>{{ contact.postJob }} - {{ contact.profession }}</p>
                <div class="contact-phone">{{ contact.phone }}</div>
              </div>
              <div class="contact-actions">
                <!-- <el-button
                  type="text"
                  icon="Phone"
                  @click.stop="callContact(contact)"
                ></el-button> -->
                <el-button
                  type="text"
                  icon="Message"
                  @click.stop="messageContact(contact)"
                ></el-button>
                <el-button
                  type="text"
                  icon="Delete"
                  :type="isInPersonalContacts(contact.id) ? 'primary' : ''"
                  @click.stop="togglePersonalContact(contact)"
                ></el-button>
              </div>
            </div>
          </div>
        </div>
      </el-tab-pane>
      <el-tab-pane label="单位通讯录" name="company">
        <div class="tab-content">
          <div class="company-contacts-layout">
            <!-- å·¦ä¾§éƒ¨é—¨æ ‘ -->
            <div class="department-tree">
              <!-- <h3>部门结构</h3>
              <el-tree
                :data="departmentTree"
                :props="{ label: 'deptName', children: 'children' }"
                node-key="deptId"
                ref="departmentTreeRef"
                highlight-current
                default-expand-all
                @node-click="handleDepartmentClick"
              /> -->
              <el-col >
                <div class="head-container">
                  <el-input
                    v-model="deptName"
                    placeholder="请输入部门名称"
                    clearable
                    prefix-icon="Search"
                    style="margin-bottom: 20px"
                  />
                </div>
                <div class="head-container">
                  <el-tree
                    :data="departmentTree"
                    :props="{ label: 'label', children: 'children' }"
                    :expand-on-click-node="false"
                    :filter-node-method="filterNode"
                    ref="deptTreeRef"
                    node-key="id"
                    highlight-current
                    default-expand-all
                    @node-click="handleDepartmentClick"
                  />
                </div>
              </el-col>
            </div>
            <!-- å³ä¾§éƒ¨é—¨æˆå‘˜ -->
            <div class="department-members">
              <h3>{{ currentDepartment?.label || '全部成员' }}</h3>
              <el-input
                v-model="companySearch.staffName"
                placeholder="搜索部门成员"
                clearable
                prefix-icon="Search"
                class="search-input"
                @keyup.enter="getCompanyContactsList"
              />
              <div class="contact-list">
                <div
                  v-for="contact in companyContacts"
                  :key="contact.id"
                  class="contact-card"
                  @click="showContactDetail(contact)"
                >
                  <div class="contact-avatar">{{ contact.staffName.charAt(0) }}</div>
                  <div class="contact-info">
                    <h4>{{ contact.staffName }}</h4>
                    <p>{{ contact.profession }}</p>
                    <div class="contact-phone">{{ contact.phone }}</div>
                  </div>
                  <div class="contact-actions">
                    <el-button
                      type="text"
                      icon="Message"
                      @click.stop="messageContact(contact)"
                    ></el-button>
                    <el-button
                      type="text"
                      icon="Delete"
                      :type="isInPersonalContacts(contact.id) ? 'primary' : ''"
                      @click.stop="togglePersonalContact(contact)"
                    ></el-button>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </el-tab-pane>
    </el-tabs>
    <!-- è”系人详情弹窗 -->
    <el-dialog
      v-model="showDetailDialog"
      title="联系人详情"
      width="400px"
    >
      <div v-if="selectedContact" class="contact-detail">
        <div class="detail-avatar">{{ selectedContact.staffName?.charAt(0) }}</div>
        <h3>{{ selectedContact.staffName }}</h3>
        <p class="detail-position">{{ selectedContact.profession }} - {{ selectedContact.postJob }}</p>
        <div class="detail-info">
          <div class="info-item">
            <span class="label">编号:</span>
            <span class="value">{{ selectedContact.staffNo }}</span>
          </div>
          <div class="info-item">
            <span class="label">手机号码:</span>
            <span class="value">{{ selectedContact.phone }}</span>
          </div>
          <div class="info-item">
            <span class="label">邮箱:</span>
            <span class="value">{{ selectedContact.sex }}</span>
          </div>
          <div class="info-item">
            <span class="label">住址:</span>
            <span class="value">{{ selectedContact.adress || '暂无' }}</span>
          </div>
          <div class="info-item">
            <span class="label">身份证号:</span>
            <span class="value">{{ selectedContact.identityCard || '暂无' }}</span>
          </div>
        </div>
      </div>
      <template #footer>
        <el-button @click="showDetailDialog = false">关闭</el-button>
        <el-button
          type="primary"
          v-if="activeTab !== 'personal'"
          @click="togglePersonalContact(selectedContact); showDetailDialog = false"
        >
          {{ isInPersonalContacts(selectedContact?.id) ? '从个人通讯录移除' : '添加到个人通讯录' }}
        </el-button>
      </template>
    </el-dialog>
    <!-- æ·»åŠ è”ç³»äººå¼¹çª— -->
    <el-dialog
      v-model="showAddContactDialog"
      title="添加联系人"
      width="500px"
    >
      <el-form :model="addContactForm" ref="addContactFormRef" label-width="80px">
        <!-- <el-form-item label="姓名" prop="name">
          <el-input v-model="addContactForm.name" placeholder="请输入姓名" />
        </el-form-item>
        <el-form-item label="手机号码" prop="phone">
          <el-input v-model="addContactForm.phone" placeholder="请输入手机号码" />
        </el-form-item>
        <el-form-item label="邮箱" prop="email">
          <el-input v-model="addContactForm.email" placeholder="请输入邮箱" />
        </el-form-item>
        <el-form-item label="部门" prop="department">
          <el-input v-model="addContactForm.department" placeholder="请输入部门" />
        </el-form-item> -->
        <el-form-item label="姓名" prop="name">
          <!-- <select v-model="addContactForm.contactId">
            <option v-for="item in EmployeeList" :key="item.id" :value="item.id">{{ item.staffName }}</option>
          </select> -->
          <el-select v-model="addContactForm.contactId" placeholder="请选择" style="width: 100%">
            <el-option
              v-for="option in EmployeeList"
              :key="option.id"
              :label="option.staffName"
              :value="option.id"
            />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="showAddContactDialog = false">取消</el-button>
        <el-button type="primary" @click="addContact">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, onMounted, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
  getPersonalContacts,
  addPersonalContact,
  removePersonalContact,
  getPublicContacts,
  getCompanyContacts,
  getDepartmentTree,
  getEmployeeDetail
} from '@/api/collaborativeApproval/enterpriseBook.js'
import { getUserProfile } from '@/api/system/user.js'
import {staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import {
  changeUserStatus,
  listUser,
  resetUserPwd,
  delUser,
  getUser,
  updateUser,
  addUser,
  deptTreeSelect,
} from "@/api/system/user";
// æ ‡ç­¾é¡µçŠ¶æ€
const activeTab = ref('personal')
const loading = ref(false)
const EmployeeList = ref([])
const page = reactive({
  pageNum: 1,
  pageSize: 10,
  total: 0,
})
// ä¸ªäººé€šè®¯å½•数据
const personalContacts = ref([])
const personalSearch = ref({
  staffName: '',
})
// å…¬å…±é€šè®¯å½•数据
const publicContacts = ref([])
const publicSearch = ref({
  staffName: '',
  staffState: 1
})
// å•位通讯录数据
const companyContacts = ref([])
const companySearch = ref({
  staffName: '',
  staffState: 1
})
const departmentTree = ref([])
const departmentTreeRef = ref(null)
const currentDepartment = ref(null)
// å¼¹çª—状态
const showDetailDialog = ref(false)
const showAddContactDialog = ref(false)
const selectedContact = ref(null)
// æ·»åŠ è”ç³»äººè¡¨å•
const addContactForm = reactive({
  contactId: '',
  name: '',
  phone: '',
  email: '',
  department: '',
  position: ''
})
const addContactFormRef = ref(null)
// åˆå§‹åŒ–数据
onMounted(() => {
  getEmployeeList()
  getPersonalContactsList()
  if (activeTab.value === 'public') {
    getPublicContactsList()
  } else if (activeTab.value === 'company') {
    getDepartmentTreeData()
    getCompanyContactsList()
  }
})
// å¤„理标签页切换
const handleTabChange = (tabName) => {
  if (tabName === 'public') {
    getPublicContactsList()
  } else if (tabName === 'company') {
    getDepartmentTreeData()
    getCompanyContactsList()
  }
}
// èŽ·å–ä¸ªäººé€šè®¯å½•åˆ—è¡¨
const getPersonalContactsList = async () => {
  loading.value = true
  getPersonalContacts(page,personalSearch.value).then(res => {
    personalContacts.value = res.data.records
  })
  loading.value = false
}
// èŽ·å–å…¬å…±é€šè®¯å½•åˆ—è¡¨
const getPublicContactsList = async () => {
  loading.value = true
  getEmployeeList()
  // publicContacts.value = generateMockPublicContacts()
  loading.value = false
}
  //获取员工列表
const getEmployeeList = async () => {
  staffJoinListPage(publicSearch.value).then(res => {
    console.log(res.data.records)
      EmployeeList.value = res.data.records
    }).catch(err => {})
}
// èŽ·å–å•ä½é€šè®¯å½•åˆ—è¡¨
const getCompanyContactsList = async () => {
  loading.value = true
    staffJoinListPage(companySearch.value).then(res => {
    // console.log(res.data.records)
      companyContacts.value = res.data.records
    }).catch(err => {})
  loading.value = false
 loading.value = false
  // }
}
// èŽ·å–éƒ¨é—¨æ ‘ç»“æž„
const getDepartmentTreeData = async () => {
    deptTreeSelect().then((response) => {
    // console.log("Tree",response.data)
    departmentTree.value = response.data;
    // enabledDeptOptions.value = filterDisabledDept(
    //   JSON.parse(JSON.stringify(response.data))
    // );
  });
}
// /** è¿‡æ»¤ç¦ç”¨çš„部门 */
// function filterDisabledDept(deptList) {
//   return deptList.filter((dept) => {
//     if (dept.disabled) {
//       return false;
//     }
//     if (dept.children && dept.children.length) {
//       dept.children = filterDisabledDept(dept.children);
//     }
//     return true;
//   });
// }
// å¤„理部门点击
const handleDepartmentClick = (data) => {
  // console.log("点击",data)
  companySearch.value = {
    ...companySearch.value,
    deptId: data.id,
  }
  // currentDepartment.value = data.id
  // èŽ·å–è¯¥éƒ¨é—¨çš„æˆå‘˜åˆ—è¡¨
  getCompanyContactsList()
}
// æ˜¾ç¤ºè”系人详情
const showContactDetail = async (contact) => {
  selectedContact.value = contact
  showDetailDialog.value = true
}
// æ‹¨æ‰“电话
const callContact = (contact) => {
  ElMessage.info(`正在拨打 ${contact.name} çš„电话: ${contact.phone}`)
}
// å‘送消息
const messageContact = (contact) => {
  ElMessage.info(`正在发送消息给 ${contact.name}`)
}
// æ·»åŠ è”ç³»äºº
const addContact = async () => {
  try {
    // è¡¨å•验证
    // if (!addContactForm.name || !addContactForm.phone) {
    //   ElMessage.warning('请填写姓名和手机号码')
    //   return
    // }
    const res = await addPersonalContact(addContactForm)
    if (res.code === 200) {
      ElMessage.success('添加成功')
      showAddContactDialog.value = false
      getPersonalContactsList()
      // é‡ç½®è¡¨å•
      Object.keys(addContactForm).forEach(key => {
        addContactForm[key] = ''
      })
    }
  } catch (error) {
    ElMessage.error('添加失败')
    // æ¨¡æ‹Ÿæ·»åŠ æˆåŠŸ
    personalContacts.value.push({
      ...addContactForm,
      id: Date.now(),
      createTime: new Date().toISOString()
    })
    ElMessage.success('添加成功')
    showAddContactDialog.value = false
    // é‡ç½®è¡¨å•
    Object.keys(addContactForm).forEach(key => {
      addContactForm[key] = ''
    })
  }
}
// ä»Žä¸ªäººé€šè®¯å½•移除
const removeFromPersonalContacts = async (contactId) => {
  ElMessageBox.confirm(
    '确定要从个人通讯录中移除该联系人吗?',
    '提示',
    {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    }
  ).then(async () => {
    try {
      const res = await removePersonalContact(contactId)
      if (res.code === 200) {
        ElMessage.success('移除成功')
        getPersonalContactsList()
      }
    } catch (error) {
      ElMessage.error('移除失败')
      // æ¨¡æ‹Ÿç§»é™¤æˆåŠŸ
      // personalContacts.value = personalContacts.value.filter(item => item.id !== contactId)
      ElMessage.success('移除成功')
    }
  })
}
// åˆ‡æ¢ä¸ªäººé€šè®¯å½•
const togglePersonalContact = async (contact) => {
  const isInPersonal = isInPersonalContacts(contact.id)
  const contactId = contact.id
  if (isInPersonal) {
    // ä»Žä¸ªäººé€šè®¯å½•移除
    //根据contactId查找personalContacts中对应的项,然后删除该项
    const index = personalContacts.value.findIndex(item => item.contactId === contactId)
    const personId = personalContacts.value[index].id
    // console.log(personId)
    await removeFromPersonalContacts(personId)
  } else {
    // æ·»åŠ åˆ°ä¸ªäººé€šè®¯å½•
    try {
      const res = await addPersonalContact({contactId: contactId})
      if (res.code === 200) {
        ElMessage.success('添加成功')
        getPersonalContactsList()
      }
    } catch (error) {
      ElMessage.error('添加失败')
      // æ¨¡æ‹Ÿæ·»åŠ æˆåŠŸ
      // personalContacts.value.push({
      //   ...contact,
      //   id: contact.id || Date.now(),
      //   createTime: new Date().toISOString()
      // })
      // ElMessage.success('添加成功')
    }
  }
}
// æ£€æŸ¥æ˜¯å¦åœ¨ä¸ªäººé€šè®¯å½•中
const isInPersonalContacts = (contactId) => {
  return personalContacts.value.some(item => item.contactId === contactId)
}
// ç”Ÿæˆæ¨¡æ‹Ÿéƒ¨é—¨æ ‘数据
const generateMockDepartmentTree = () => {
  return [
    {
      deptId: 1,
      deptName: '技术部',
      children: [
        {
          deptId: 101,
          deptName: '前端组'
        },
        {
          deptId: 102,
          deptName: '后端组'
        },
        {
          deptId: 103,
          deptName: '测试组'
        }
      ]
    },
    {
      deptId: 2,
      deptName: '产品部'
    },
    {
      deptId: 3,
      deptName: '人事部'
    },
    {
      deptId: 4,
      deptName: '财务部'
    }
  ]
}
// ç”Ÿæˆæ¨¡æ‹Ÿå•位通讯录数据
// const generateMockCompanyContacts = (deptName) => {
//   const allContacts = getEmployeeList()
//   if (deptName) {
//     return allContacts.filter(contact => contact.postJob === deptName)
//   }
//   return allContacts
// }
</script>
<style scoped>
.header {
  margin-bottom: 20px;
  padding: 15px;
  background: #f5f7fa;
  border-radius: 8px;
}
.header h2 {
  margin: 0 0 5px 0;
  color: #303133;
}
.header p {
  margin: 0;
  color: #909399;
  font-size: 14px;
}
.tab-content {
  padding: 15px;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.search-input {
  margin-bottom: 20px;
  width: 300px;
}
.contact-list {
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
}
.contact-card {
  display: flex;
  align-items: center;
  padding: 15px;
  width: 500px;
  background: #f8f9fa;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.3s;
}
.contact-card:hover {
  background: #e9ecef;
  transform: translateY(-2px);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.contact-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  background: #409eff;
  color: #fff;
  font-size: 20px;
  font-weight: bold;
  border-radius: 50%;
  margin-right: 15px;
}
.contact-info {
  flex: 1;
}
.contact-info h4 {
  margin: 0 0 5px 0;
  color: #303133;
  font-size: 16px;
}
.contact-info p {
  margin: 0 0 5px 0;
  color: #606266;
  font-size: 14px;
}
.contact-phone {
  color: #409eff;
  font-size: 14px;
}
.contact-actions {
  display: flex;
  gap: 5px;
}
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 60px 20px;
  color: #909399;
}
.company-contacts-layout {
  display: flex;
  gap: 20px;
}
.department-tree {
  width: 250px;
  padding: 15px;
  background: #f8f9fa;
  border-radius: 8px;
}
.department-tree h3 {
  margin: 0 0 15px 0;
  padding-bottom: 10px;
  border-bottom: 1px solid #e4e7ed;
  color: #303133;
  font-size: 16px;
}
.department-members {
  flex: 1;
}
.department-members h3 {
  margin: 0 0 15px 0;
  color: #303133;
  font-size: 16px;
}
.contact-detail {
  text-align: center;
}
.detail-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 80px;
  height: 80px;
  background: #409eff;
  color: #fff;
  font-size: 32px;
  font-weight: bold;
  border-radius: 50%;
  margin: 0 auto 20px;
}
.contact-detail h3 {
  margin: 0 0 10px 0;
  color: #303133;
  font-size: 20px;
}
.detail-position {
  margin: 0 0 30px 0;
  color: #606266;
  font-size: 14px;
}
.detail-info {
  text-align: left;
}
.info-item {
  margin-bottom: 15px;
}
.info-item .label {
  display: inline-block;
  width: 100px;
  color: #909399;
  font-size: 14px;
}
.info-item .value {
  color: #303133;
  font-size: 14px;
}
</style>
src/views/collaborativeApproval/knowledgeBase/index.vue
@@ -233,7 +233,7 @@
import { onMounted, ref, reactive, toRefs } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import { listKnowledgeBase, delKnowledgeBaseBatch,addKnowledgeBase,updateKnowledgeBase } from "@/api/collaborativeApproval/knowledgeBase.js";
import { listKnowledgeBase, delKnowledgeBase,addKnowledgeBase,updateKnowledgeBase } from "@/api/collaborativeApproval/knowledgeBase.js";
// è¡¨å•验证规则
const rules = {
@@ -729,17 +729,14 @@
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    // ä»ŽmockData中删除选中的项
    selectedIds.value.forEach(id => {
      const index = mockData.findIndex(item => item.id === id);
      if (index !== -1) {
        mockData.splice(index, 1);
    // console.log(selectedIds.value);
    delKnowledgeBase(selectedIds.value).then(res => {
      if(res.code == 200){
        ElMessage.success("删除成功");
        selectedIds.value = [];
        getList();
      }
    });
    ElMessage.success("删除成功");
    selectedIds.value = [];
    getList();
    })
  }).catch(() => {
    // ç”¨æˆ·å–消
  });
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,746 @@
<template>
  <div class="app-container">
        <!-- è§„章制度管理-->
          <el-card class="box-card">
            <template #header>
              <div class="card-header">
                <span>规章制度发布</span>
              </div>
            </template>
            <div class="tab-content">
              <el-row :gutter="20" class="mb-20">
                <span class="ml-10">制度标题:</span>
                <el-col :span="6">
                  <el-input v-model="regulationSearchForm.title" placeholder="请输入制度标题" clearable />
                </el-col>
                <span class="search_title">制度分类:</span>
                <el-col :span="4">
                  <el-select v-model="regulationSearchForm.category" placeholder="制度分类" clearable>
                    <el-option label="人事制度" value="hr" />
                    <el-option label="财务制度" value="finance" />
                    <el-option label="安全制度" value="safety" />
                    <el-option label="技术制度" value="tech" />
                  </el-select>
                </el-col>
                <el-col :span="8">
                  <el-button type="primary" @click="searchRegulations">搜索</el-button>
                  <el-button @click="resetRegulationSearch">重置</el-button>
                  <el-button type="success" @click="handleAdd">
                    å‘布制度
                  </el-button>
                </el-col>
              </el-row>
              <el-table :data="regulations" border v-loading="tableLoading"  style="width: 100%">
                <el-table-column prop="regulationNum" label="制度编号" width="120" />
                <el-table-column prop="title" label="制度标题" min-width="150" />
                <el-table-column prop="category" label="分类" width="120">
                  <template #default="scope">
                    <el-tag>{{ getCategoryText(scope.row.category) }}</el-tag>
                  </template>
                </el-table-column>
                <el-table-column prop="version" label="版本" width="120" />
                <el-table-column prop="createUserName" label="发布人" width="120" />
                <el-table-column prop="createTime" label="发布时间" width="180" />
                <el-table-column prop="status" label="状态" width="100">
                  <template #default="scope">
                    <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
                      {{ scope.row.status === 'active' ? '生效中' : '已废止' }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column prop="readCount" label="已读人数" width="100" />
                <el-table-column label="操作" width="250" fixed="right">
                  <template #default="scope">
                    <el-button link @click="viewRegulation(scope.row)">查看</el-button>
                    <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
                    <el-button link type="danger" @click="repealEdit(scope.row)">废弃</el-button>
                    <el-button link type="success" @click="viewVersionHistory(scope.row)">版本历史</el-button>
                    <el-button link type="warning" @click="viewReadStatus(scope.row)">阅读状态</el-button>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </el-card>
    <!-- ç”¨å°ç”³è¯·å¯¹è¯æ¡† -->
    <!-- <el-dialog v-model="showSealApplyDialog" title="申请用印" width="600px">
      <el-form :model="sealForm" :rules="sealRules" ref="sealFormRef" label-width="100px">
        <el-form-item label="申请编号" prop="applicationNum">
          <el-input v-model="sealForm.applicationNum" placeholder="请输入申请编号" />
        </el-form-item>
        <el-form-item label="申请标题" prop="title">
          <el-input v-model="sealForm.title" placeholder="请输入申请标题" />
        </el-form-item>
        <el-form-item label="用印类型" prop="sealType">
          <el-select v-model="sealForm.sealType" placeholder="请选择用印类型" style="width: 100%">
            <el-option label="公章" value="official" />
            <el-option label="合同专用章" value="contract" />
            <el-option label="财务专用章" value="finance" />
            <el-option label="法人章" value="legal" />
          </el-select>
        </el-form-item>
        <el-form-item label="申请原因" prop="reason">
          <el-input v-model="sealForm.reason" type="textarea" :rows="4" placeholder="请详细说明用印原因" />
        </el-form-item>
        <el-form-item label="紧急程度" prop="urgency">
          <el-radio-group v-model="sealForm.urgency">
            <el-radio label="normal">普通</el-radio>
            <el-radio label="urgent">紧急</el-radio>
            <el-radio label="very-urgent">特急</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="showSealApplyDialog = false">取消</el-button>
          <el-button type="primary" @click="submitSealApplication">提交申请</el-button>
        </span>
      </template>
    </el-dialog> -->
    <!-- è§„章制度发布对话框 -->
    <el-dialog v-model="showRegulationDialog" :title="operationType === 'add' ? '发布制度' : '编辑制度'" width="800px">
      <el-form :model="regulationForm" :rules="regulationRules" ref="regulationFormRef" label-width="100px">
        <el-form-item label="制度编号" prop="regulationNum">
          <el-input v-model="regulationForm.regulationNum" placeholder="请输入制度编号" />
        </el-form-item>
        <el-form-item label="制度标题" prop="title">
          <el-input v-model="regulationForm.title" placeholder="请输入制度标题" />
        </el-form-item>
        <el-form-item label="制度分类" prop="category">
          <el-select v-model="regulationForm.category" placeholder="请选择制度分类" style="width: 100%">
            <el-option label="人事制度" value="hr" />
            <el-option label="财务制度" value="finance" />
            <el-option label="安全制度" value="safety" />
            <el-option label="技术制度" value="tech" />
          </el-select>
        </el-form-item>
        <el-form-item label="制度内容" prop="content">
          <el-input v-model="regulationForm.content" type="textarea" :rows="10" placeholder="请输入制度详细内容" />
        </el-form-item>
        <el-form-item label="制度版本" prop="version">
          <el-input v-model="regulationForm.version" placeholder="请输入制度版本" />
        </el-form-item>
        <el-form-item label="生效时间" prop="effectiveTime">
          <el-date-picker v-model="regulationForm.effectiveTime" type="datetime" format="YYYY-MM-DD HH:mm:ss"
             value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择生效时间" style="width: 100%" />
        </el-form-item>
        <el-form-item label="适用范围" prop="scope">
          <el-checkbox-group v-model="regulationForm.scope">
            <el-checkbox label="all">全体员工</el-checkbox>
            <el-checkbox label="manager">管理层</el-checkbox>
            <el-checkbox label="hr">人事部门</el-checkbox>
            <el-checkbox label="finance">财务部门</el-checkbox>
            <el-checkbox label="tech">技术部门</el-checkbox>
          </el-checkbox-group>
        </el-form-item>
        <el-form-item label="是否需要确认" prop="requireConfirm">
          <el-switch v-model="regulationForm.requireConfirm" />
          <span class="ml-10">开启后员工需要阅读确认</span>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="showRegulationDialog = false">取消</el-button>
          <el-button type="primary" @click="submitRegulation">发布制度</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- ç”¨å°è¯¦æƒ…对话框 -->
    <!-- <el-dialog v-model="showSealDetailDialog" title="用印申请详情" width="700px">
      <div v-if="currentSealDetail" class="mb10">
        <el-descriptions :column="2" border>
          <el-descriptions-item label="申请编号">{{ currentSealDetail.id }}</el-descriptions-item>
          <el-descriptions-item label="申请标题">{{ currentSealDetail.title }}</el-descriptions-item>
          <el-descriptions-item label="申请人">{{ currentSealDetail.createUserName }}</el-descriptions-item>
          <el-descriptions-item label="所属部门">{{ currentSealDetail.department }}</el-descriptions-item>
          <el-descriptions-item label="用印类型">{{ getSealTypeText(currentSealDetail.sealType) }}</el-descriptions-item>
          <el-descriptions-item label="申请时间">{{ currentSealDetail.createTime }}</el-descriptions-item>
          <el-descriptions-item label="状态">
            <el-tag :type="getStatusType(currentSealDetail.status)">
              {{ getStatusText(currentSealDetail.status) }}
            </el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="申请原因" :span="2">{{ currentSealDetail.reason }}</el-descriptions-item>
        </el-descriptions>
      </div>
    </el-dialog> -->
    <!-- è§„章制度详情对话框 -->
    <el-dialog v-model="showRegulationDetailDialog" title="规章制度详情" width="800px">
      <div v-if="currentRegulationDetail">
        <el-descriptions :column="2" border>
          <el-descriptions-item label="制度编号">{{ currentRegulationDetail.id }}</el-descriptions-item>
          <el-descriptions-item label="制度标题">{{ currentRegulationDetail.title }}</el-descriptions-item>
          <el-descriptions-item label="分类">{{ getCategoryText(currentRegulationDetail.category) }}</el-descriptions-item>
          <el-descriptions-item label="版本">{{ currentRegulationDetail.version }}</el-descriptions-item>
          <el-descriptions-item label="发布人">{{ currentRegulationDetail.createUserName }}</el-descriptions-item>
          <el-descriptions-item label="发布时间">{{ currentRegulationDetail.createTime }}</el-descriptions-item>
        </el-descriptions>
        <div class="mt-20">
          <h4>制度内容</h4>
          <div class="regulation-content">{{ currentRegulationDetail.content }}</div>
        </div>
        <!-- å¦‚æžœtableData>0 æ˜¾ç¤º -->
        <div style="margin: 10px 0;" v-if="tableData && tableData.length > 0" >
          <el-button type="success" @click="resetForm(currentRegulationDetail)">确认查看</el-button>
        </div>
      </div>
    </el-dialog>
    <!-- ç‰ˆæœ¬åŽ†å²å¯¹è¯æ¡† -->
    <el-dialog v-model="showVersionHistoryDialog" title="版本历史" width="800px">
      <el-table :data="versionHistory" style="width: 100%;margin-bottom: 10px">
        <el-table-column prop="version" label="版本号" width="100" />
        <el-table-column prop="updateTime" label="更新时间" width="180" />
        <el-table-column prop="createUserName" label="更新人" width="120" />
        <el-table-column prop="changeLog" label="变更说明">
          <template #default="scope">
            <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
              {{ scope.row.status === 'active' ? '生效中' : '已废止' }}
            </el-tag>
          </template>
        </el-table-column>
      </el-table>
    </el-dialog>
    <!-- é˜…读状态对话框 -->
    <el-dialog v-model="showReadStatusDialog" title="阅读状态" width="800px">
      <el-table :data="readStatusList" style="width: 100%;margin-bottom: 10px">
        <el-table-column prop="employee" label="员工姓名" width="120" />
        <el-table-column prop="department" label="所属部门" width="150" />
        <el-table-column prop="createTime" label="阅读时间" width="180" />
        <el-table-column prop="confirmTime" label="确认时间" width="180" />
        <el-table-column prop="status" label="状态" width="100">
          <template #default="scope">
            <el-tag :type="scope.row.status === 'confirmed' ? 'success' : 'warning'">
              {{ scope.row.status === 'confirmed' ? '已确认' : '未确认' }}
            </el-tag>
          </template>
        </el-table-column>
      </el-table>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { listSealApplication, addSealApplication, updateSealApplication,listRuleManagement,addRuleManagement,updateRuleManagement,delRuleManagement,getReadingStatusByRuleId,getReadingStatusList,addReadingStatus,updateReadingStatus  } from '@/api/collaborativeApproval/sealManagement.js'
import { el } from 'element-plus/es/locales.mjs'
import { getUserProfile } from '@/api/system/user.js'
import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import useUserStore from '@/store/modules/user'
import { userLoginFacotryList } from "@/api/system/user.js"
// å“åº”式数据
const currentUser = ref(null)
const activeTab = ref('seal')
const operationType = ref('add')
const tableData = ref([])
// ç”¨å°ç”³è¯·ç›¸å…³
const userStore = useUserStore()
const showSealApplyDialog = ref(false)
const tableLoading = ref(false)
const showSealDetailDialog = ref(false)
const currentSealDetail = ref(null)
const sealFormRef = ref()
const sealForm = reactive({
  applicationNum: '',
  title: '',
  sealType: '',
  reason: '',
  urgency: 'normal',
  status: 'pending'
})
const sealRules = {
  applicationNum: [{ required: true, message: '请输入申请编号', trigger: 'blur' }],
  title: [{ required: true, message: '请输入申请标题', trigger: 'blur' }],
  sealType: [{ required: true, message: '请选择用印类型', trigger: 'change' }],
  reason: [{ required: true, message: '请输入申请原因', trigger: 'blur' }]
}
const sealSearchForm = reactive({
  title: '',
  status: ''
})
// åˆ†é¡µå‚æ•°
const page = reactive({
  current: 1,
  size: 10,
  total: 0
})
// è§„章制度相关
const showRegulationDialog = ref(false)
const showRegulationDetailDialog = ref(false)
const showVersionHistoryDialog = ref(false)
const showReadStatusDialog = ref(false)
const currentRegulationDetail = ref(null)
const regulationFormRef = ref()
const regulationForm = reactive({
  id: '',
  regulationNum: '',
  title: '',
  category: '',
  content: '',
  version: '',
  status: 'active',
  readCount: 0,
  effectiveTime: '',
  scope: [],
  requireConfirm: false
})
const readStatus = ref({
  id: '',
  ruleId: '',
  employee: '',
  department: '',
  createTime: '',
  confirmTime: '',
  status: 'unconfirmed'
})
const regulationRules = {
  title: [{ required: true, message: '请输入制度标题', trigger: 'blur' }],
  category: [{ required: true, message: '请选择制度分类', trigger: 'change' }],
  content: [{ required: true, message: '请输入制度内容', trigger: 'blur' }],
  effectiveTime: [{ required: true, message: '请选择生效时间', trigger: 'change' }],
  scope: [{ required: true, message: '请选择适用范围', trigger: 'change' }]
}
const regulationSearchForm = reactive({
  title: '',
  category: ''
})
// å‡æ•°æ®
const sealApplications = ref([])
const regulations = ref([])
const versionHistory = ref([])
const readStatusList = ref([])
  // { employee: '陈志强', department: '销售部', readTime: '2025-01-11 10:30:00', confirmTime: '2025-01-11 10:35:00', status: 'confirmed' },
  // { employee: '刘雅婷', department: '技术部', readTime: '2025-01-11 14:20:00', confirmTime: '', status: 'unconfirmed' },
  // { employee: '王建国', department: '财务部', readTime: '2025-01-12 09:15:00', confirmTime: '2025-01-12 09:20:00', status: 'confirmed' }
// ç”¨å°ç”³è¯·çŠ¶æ€
const getStatusType = (status) => {
  const statusMap = {
    pending: 'warning',
    approved: 'success',
    rejected: 'danger'
  }
  return statusMap[status] || 'info'
}
// åˆ¶åº¦çŠ¶æ€
const getStatusText = (status) => {
  const statusMap = {
    pending: '待审批',
    approved: '已通过',
    rejected: '已拒绝'
  }
  return statusMap[status] || '未知'
}
// ç”¨å°ç±»åž‹
const getSealTypeText = (sealType) => {
  const sealTypeMap = {
    official: '公章',
    contract: '合同专用章',
    finance: '财务专用章',
    tegal: '技术专用章'
  }
  return sealTypeMap[sealType] || '未知'
}
// åˆ¶åº¦åˆ†ç±»
const getCategoryText = (category) => {
  const categoryMap = {
    hr: '人事制度',
    finance: '财务制度',
    safety: '安全制度',
    tech: '技术制度'
  }
  return categoryMap[category] || '未知'
}
// æœç´¢å°ç« ç”³è¯·
const searchSealApplications = () => {
  page.current=1
  getSealApplicationList()
  // ElMessage.success('搜索完成')
}
// é‡ç½®å°ç« ç”³è¯·æœç´¢
const resetSealSearch = () => {
  sealSearchForm.title = ''
  sealSearchForm.status = ''
  searchSealApplications()
}
// æœç´¢åˆ¶åº¦
const searchRegulations = () => {
  page.current=1
  getRegulationList()
}
// é‡ç½®åˆ¶åº¦æœç´¢
const resetRegulationSearch = () => {
  regulationSearchForm.title = ''
  regulationSearchForm.category = ''
  searchRegulations()
}
// æäº¤ç”¨å°ç”³è¯·
const submitSealApplication = async () => {
  try {
    await sealFormRef.value.validate()
    addSealApplication(sealForm).then(res => {
      if(res.code == 200){
        ElMessage.success('申请提交成功')
        showSealApplyDialog.value = false
        getSealApplicationList()
        Object.assign(sealForm, {
        applicationNum: '',
        title: '',
        sealType: '',
        reason: '',
        urgency: 'normal',
        status: 'pending'
      })
      }
    }).catch(err => {
      ElMessage.error(err.msg)
    })
  } catch (error) {
    ElMessage.error('请完善申请信息')
  }
}
// æ–°å¢ž
const handleAdd = () => {
  operationType.value = 'add'
  resetRegulationForm()
  showRegulationDialog.value = true
}
// ç¼–辑
const handleEdit = (row) => {
  operationType.value = 'edit'
  Object.assign(regulationForm, row)
  showRegulationDialog.value = true
}
// åºŸå¼ƒ
const repealEdit = (row) => {
  operationType.value = 'edit'
  Object.assign(regulationForm, row)
  regulationForm.status = 'repealed'
  ElMessageBox.confirm('确认废弃该制度?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    updateRuleManagement(regulationForm).then(res => {
      if(res.code == 200){
        ElMessage.success('制度废弃成功')
        // showRegulationDialog.value = false
        getRegulationList()
        resetRegulationForm()
      }
    })
  }).catch(() => {
    ElMessage({
      type: 'info',
      message: '已取消废弃'
    })
  })
}
// å‘布制度
const submitRegulation = async () => {
  try {
    await regulationFormRef.value.validate()
    if(operationType.value == 'add'){
      addRuleManagement(regulationForm).then(res => {
        if(res.code == 200){
          ElMessage.success('制度发布成功')
          showRegulationDialog.value = false
          getRegulationList()
          resetRegulationForm()
        }
      })
    }else{
      updateRuleManagement(regulationForm).then(res => {
        if(res.code == 200){
          ElMessage.success('制度编辑成功')
          showRegulationDialog.value = false
          resetRegulationForm()
          getRegulationList()
      }})}
  }catch(err){
    ElMessage.error(err.msg)
  }
}
//重置制度表单
const resetRegulationForm = () => {
  Object.assign(regulationForm, {
    id: '',
    regulationNum: '',
    title: '',
    category: '',
    content: '',
    version: '',
    status: 'active',
    readCount: 0,
    effectiveTime: '',
    scope: [],
    requireConfirm: false
})
}
// æŸ¥çœ‹ç”¨å°ç”³è¯·è¯¦æƒ…
const viewSealDetail = (row) => {
  currentSealDetail.value = row
  showSealDetailDialog.value = true
}
// å®¡æ‰¹ç”¨å°ç”³è¯·
const approveSeal = (row) => {
  console.log(row)
  ElMessageBox.confirm('确认通过该用印申请?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    row.status = 'approved'
    updateSealApplication(row).then(res => {
      if(res.code == 200){
        ElMessage.success('审批通过')
      }
    })
  })
}
// æ‹’绝用印申请
const rejectSeal = (row) => {
  ElMessageBox.prompt('请输入拒绝原因', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    inputPattern: /\S+/,
    inputErrorMessage: '拒绝原因不能为空'
  }).then(({ value }) => {
    row.status = 'rejected'
    updateSealApplication(row).then(res => {
      if(res.code == 200){
        ElMessage.success('审批拒绝')
      }
    })
    ElMessage.success('已拒绝申请')
  })
}
// èŽ·å–åœ¨èŒå‘˜å·¥åˆ—è¡¨
const getList = () => {
  tableLoading.value = true;
      //获取当前登录用户信息
  getUserProfile().then(res => {
    if(res.code == 200){
      console.log(res.data.userName)
      currentUser.value = res.data.userName
    }
  })
  staffJoinListPage({staffState: 1}).then(res => {
    tableLoading.value = false;
    // tableData.value = res.data.records
    // //筛选出和currentUser同名的人员
    tableData.value = res.data.records.filter(item => item.staffName === currentUser.value)
    console.log("tableData",tableData.value)
    page.total = res.data.total;
    if(tableData.value.length == 0){
    ElMessage.error('当前用户未加入任何部门')
    }
  }).catch(err => {
    tableLoading.value = false;
  })
};
// æŸ¥çœ‹åˆ¶åº¦ç‰ˆæœ¬åŽ†å²
const viewVersionHistory = (row) => {
  showVersionHistoryDialog.value = true
  const params = {
    category: row.category
  }
  listRuleManagement(page,params).then(res => {
    if(res.code == 200){
      versionHistory.value = res.data.records
    }
  })
}
// æŸ¥çœ‹åˆ¶åº¦è¯¦æƒ…
const viewRegulation = (row) => {
  getList()
  currentRegulationDetail.value = row
  showRegulationDetailDialog.value = true
  getReadingStatusByRuleId(row.id).then(res => {
    if(res.code == 200){
      readStatusList.value = res.data
      if(readStatusList.value.length==0 && tableData.value.length>0){
          const params = {
          ruleId: row.id,
          employee: tableData.value[0].staffName,
          department: tableData.value[0].postJob,
          status: 'unconfirmed'
        }
        addReadingStatus(params).then(res => {
          if(res.code == 200){
            ElMessage.success('制度阅读成功')
          }
        })
      }
    }
  })
}
// æŸ¥çœ‹åˆ¶åº¦é˜…读状态
const viewReadStatus = (row) => {
  showReadStatusDialog.value = true
  //查看阅读状态列表
  getReadingStatusByRuleId(row.id).then(res => {
    if(res.code == 200){
      readStatusList.value = res.data
    }
  })
}
//确认查看
const resetForm = (row) => {
  console.log("row",row)
  row.readCount = row.readCount + 1
  updateRuleManagement(row).then(res => {
    if(res.code == 200){
      ElMessage.success('查看数量修改成功')
      //修改阅读状态
      //根据制度id和当前登录的员工得到阅读状态
      // let item = readStatusList.value.filter(item => item.employee == tableData.value[0].staffName )
      // if(item.length>0){
      //   item[0].status = 'confirmed',
      //   item[0].confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0];
      // }
      // ç­›é€‰å½“前员工对应该制度的阅读状态记录
      let statusItem = readStatusList.value.find(item => item.employee === tableData.value[0].staffName && item.ruleId === row.id);
      if (statusItem) {
        // å¦‚果找到记录,更新状态和确认时间
        statusItem.status = 'confirmed';
        // æ ¼å¼åŒ–时间为"YYYY-MM-DD HH:mm:ss"格式
        const now = new Date();
        statusItem.confirmTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
        // statusItem.confirmTime = new Date().toISOString().replace('T', ' ').split('.')[0];
        updateReadingStatus(statusItem).then(res => {
          if(res.code == 200){
            ElMessage.success('制度阅读状态修改成功')
          }
        })
      }
    }
  })
}
// èŽ·å–å°ç« ç”³è¯·åˆ—è¡¨æ•°æ®
const getSealApplicationList = async () => {
  tableLoading.value = true
  listSealApplication(page,sealSearchForm)
  .then(res => {
    //获取当前登录的部门信息
// èŽ·å–å½“å‰ç™»å½•çš„éƒ¨é—¨ä¿¡æ¯å¹¶è¿‡æ»¤æ•°æ®
    const currentFactoryName = userStore.currentFactoryName
    if (currentFactoryName) {
      // æ ¹æ®currentFactoryName过滤出department相同的数据
      sealApplications.value = res.data.records.filter(item => item.department === currentFactoryName)
      // æ›´æ–°è¿‡æ»¤åŽçš„æ€»æ•°
      page.value.total = sealApplications.value.length
    } else {
      // å¦‚果没有currentFactoryName,则显示所有数据
      sealApplications.value = res.data.records
      page.value.total = res.data.total
    }
    // sealApplications.value = res.data.records
    // page.value.total = res.data.total;
    tableLoading.value = false;
  }).catch(err => {
    tableLoading.value = false;
  })
}
// èŽ·å–è§„ç« åˆ¶åº¦åˆ—è¡¨æ•°æ®
const getRegulationList = async () => {
  tableLoading.value = true
  listRuleManagement(page,regulationSearchForm)
  .then(res => {
    regulations.value = res.data.records
    // è¿‡æ»¤æŽ‰å·²åºŸå¼ƒçš„制度
    // regulations.value = res.data.records.filter(item => item.status !== 'repealed')
    page.value.total = res.data.total;
    tableLoading.value = false;
  }).catch(err => {
    tableLoading.value = false;
  })
}
onMounted(() => {
  // åˆå§‹åŒ–
  getSealApplicationList()
  getRegulationList()
})
</script>
<style scoped>
.app-container {
  padding: 20px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.tab-content {
  padding: 20px 0;
}
.mb-20 {
  margin-bottom: 20px;
}
.mt-20 {
  margin-top: 20px;
}
.ml-10 {
  margin-left: 10px;
}
.regulation-content {
  background-color: #f5f5f5;
  padding: 15px;
  border-radius: 4px;
  line-height: 1.6;
  white-space: pre-wrap;
  height: 200px;
}
.dialog-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
</style>
src/views/collaborativeApproval/sealManagement/index.vue
@@ -3,22 +3,19 @@
    <el-card class="box-card">
      <template #header>
        <div class="card-header">
          <span>用印管理与规章制度发布</span>
          <el-button type="primary" @click="showSealApplyDialog = true">
            <el-icon><Plus /></el-icon>
            ç”³è¯·ç”¨å°
          </el-button>
          <span>用印管理发布</span>
        </div>
      </template>
      <el-tabs v-model="activeTab" type="border-card">
        <!-- ç”¨å°ç”³è¯·ç®¡ç† -->
        <el-tab-pane label="用印申请管理" name="seal">
          <div class="tab-content">
            <el-row :gutter="20" class="mb-20">
   <!-- ç”¨å°ç”³è¯·ç®¡ç† -->
        <div class="tab-content">
            <el-row :gutter="20" class="mb-20 ">
              <span class="ml-10">用印标题:</span>
              <el-col :span="6">
                <el-input v-model="sealSearchForm.title" placeholder="请输入申请标题" clearable />
              </el-col>
              <span class="search_title">审批状态:</span>
              <el-col :span="4">
                <el-select v-model="sealSearchForm.status" placeholder="审批状态" clearable>
                  <el-option label="待审批" value="pending" />
@@ -29,6 +26,8 @@
              <el-col :span="4">
                <el-button type="primary" @click="searchSealApplications">搜索</el-button>
                <el-button @click="resetSealSearch">重置</el-button>
                <el-button type="primary" @click="showSealApplyDialog = true">申请用印
                </el-button>
              </el-col>
            </el-row>
@@ -72,12 +71,16 @@
                </template>
              </el-table-column>
            </el-table>
          </div>
        </el-tab-pane>
        </div>
    </el-card>
        <!-- è§„章制度管理 -->
        <el-tab-pane label="规章制度管理" name="regulations">
          <div class="tab-content">
          <!-- <div class="tab-content">
            <el-row :gutter="20" class="mb-20">
              <el-col :span="6">
                <el-input v-model="regulationSearchForm.title" placeholder="请输入制度标题" clearable />
@@ -128,19 +131,9 @@
                </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>
        </el-tab-pane>
      </el-tabs>
    </el-card>
          </div> -->
    <!-- ç”¨å°ç”³è¯·å¯¹è¯æ¡† -->
    <el-dialog v-model="showSealApplyDialog" title="申请用印" width="600px">
@@ -179,7 +172,7 @@
    </el-dialog>
    <!-- è§„章制度发布对话框 -->
    <el-dialog v-model="showRegulationDialog" :title="operationType === 'add' ? '发布制度' : '编辑制度'" width="800px">
    <!-- <el-dialog v-model="showRegulationDialog" :title="operationType === 'add' ? '发布制度' : '编辑制度'" width="800px">
      <el-form :model="regulationForm" :rules="regulationRules" ref="regulationFormRef" label-width="100px">
        <el-form-item label="制度编号" prop="regulationNum">
          <el-input v-model="regulationForm.regulationNum" placeholder="请输入制度编号" />
@@ -225,7 +218,7 @@
          <el-button type="primary" @click="submitRegulation">发布制度</el-button>
        </span>
      </template>
    </el-dialog>
    </el-dialog> -->
    <!-- ç”¨å°è¯¦æƒ…对话框 -->
    <el-dialog v-model="showSealDetailDialog" title="用印申请详情" width="700px">
@@ -312,6 +305,8 @@
import { el } from 'element-plus/es/locales.mjs'
import { getUserProfile } from '@/api/system/user.js'
import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import useUserStore from '@/store/modules/user'
import { userLoginFacotryList } from "@/api/system/user.js"
// å“åº”式数据
const currentUser = ref(null)
@@ -319,6 +314,7 @@
const operationType = ref('add')
const tableData = ref([])
// ç”¨å°ç”³è¯·ç›¸å…³
const userStore = useUserStore()
const showSealApplyDialog = ref(false)
const tableLoading = ref(false)
const showSealDetailDialog = ref(false)
@@ -727,16 +723,26 @@
  })
}
// èŽ·å–å°ç« ç”³è¯·åˆ—è¡¨æ•°æ®
const getSealApplicationList = async () => {
  tableLoading.value = true
  listSealApplication(page,sealSearchForm)
  .then(res => {
    sealApplications.value = res.data.records
    page.value.total = res.data.total;
    //获取当前登录的部门信息
// èŽ·å–å½“å‰ç™»å½•çš„éƒ¨é—¨ä¿¡æ¯å¹¶è¿‡æ»¤æ•°æ®
    const currentFactoryName = userStore.currentFactoryName
    if (currentFactoryName) {
      // æ ¹æ®currentFactoryName过滤出department相同的数据
      sealApplications.value = res.data.records.filter(item => item.department === currentFactoryName)
      // æ›´æ–°è¿‡æ»¤åŽçš„æ€»æ•°
      page.value.total = sealApplications.value.length
    } else {
      // å¦‚果没有currentFactoryName,则显示所有数据
      sealApplications.value = res.data.records
      page.value.total = res.data.total
    }
    // sealApplications.value = res.data.records
    // page.value.total = res.data.total;
    tableLoading.value = false;
  }).catch(err => {
src/views/equipmentManagement/defectManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,221 @@
<template>
  <div class="defect-management">
    <!-- æ“ä½œæŒ‰é’® -->
    <div class="actions">
      <el-button type="primary" @click="showRegisterDialog = true">登记缺陷</el-button>
    </div>
    <!-- ç¼ºé™·åˆ—表 -->
    <el-table :data="defectList" style="width: 100%; margin-top: 10px;" border>
      <el-table-column prop="deviceName" label="设备名称" width="180"></el-table-column>
      <el-table-column prop="defectDescription" label="缺陷描述" win-width="300"></el-table-column>
      <el-table-column prop="status" label="状态" width="220">
        <template #default="{ row }">
          <el-tag :type="row.status === '严重缺陷' ? 'danger' : 'success'">
            {{ row.status }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="操作" width="220">
        <template #default="{ row }">
          <el-button
            v-if="row.status === '严重缺陷' || row.status === '一般缺陷'"
            type="text"
            @click="eliminateDefect(row)"
          >
            æ¶ˆé™¤ç¼ºé™·
          </el-button>
          <!-- <el-button
            v-if="row.status === '严重缺陷'"
            type="text"
            @click="transferToRepairOrder(row.id)"
          >
            è½¬ç»´ä¿®å•
          </el-button> -->
          <el-button type="text" @click="getLedger(row.deviceLedgerId)">
            æŸ¥çœ‹å°è´¦
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- ç¼ºé™·ç™»è®°å¯¹è¯æ¡† -->
    <el-dialog title="登记设备缺陷" v-model="showRegisterDialog" width="50%">
      <el-form :model="defectForm" :rules="defectRules" ref="defectFormRef" label-width="100px">
        <el-form-item label="设备名称" prop="deviceName">
          <el-select v-model="defectForm.deviceLedgerId" @change="setDeviceModel">
            <el-option
              v-for="(item, index) in deviceOptions"
              :key="index"
              :label="item.deviceName"
              :value="item.id"
            ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="缺陷描述" prop="defectDescription">
          <el-input type="textarea" v-model="defectForm.defectDescription"></el-input>
        </el-form-item>
        <el-form-item label="设备状态" prop="status">
          <el-radio-group v-model="defectForm.status">
            <el-radio label="正常">正常</el-radio>
            <el-radio label="一般缺陷">一般缺陷</el-radio>
            <el-radio label="严重缺陷">严重缺陷</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="showRegisterDialog = false">取消</el-button>
          <el-button type="primary" @click="submitDefectForm">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- ç¼ºé™·è®¾å¤‡å°è´¦å¯¹è¯æ¡† -->
    <el-dialog title="缺陷设备台账" v-model="showLedgerDialog" width="80%">
      <el-table :data="ledgerList" style="width: 100%; margin-top: 10px;" border>
        <el-table-column prop="deviceName" label="设备名称"></el-table-column>
        <el-table-column prop="defectDescription" label="缺陷描述"></el-table-column>
        <el-table-column prop="status" label="状态"></el-table-column>
        <el-table-column prop="eliminateTime" label="消缺时间"></el-table-column>
      </el-table>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
// å‡è®¾ä»¥ä¸‹æ˜¯åŽç«¯æŽ¥å£
import {
  registerDefect,
  getDefectList,
  eliminateDefect as apiEliminateDefect,
  getDefectLedger,
  deleteDefect
} from '@/api/equipmentManagement/defectManagement';
// ç¼ºé™·åˆ—表
const defectList = ref([]);
// ç™»è®°å¯¹è¯æ¡†æ˜¾ç¤ºçŠ¶æ€
const showRegisterDialog = ref(false);
// å°è´¦å¯¹è¯æ¡†æ˜¾ç¤ºçŠ¶æ€
const showLedgerDialog = ref(false);
// ç¼ºé™·è¡¨å•
const defectForm = reactive({
  deviceLedgerId: '',
  defectDescription: '',
  status: '',
});
const deviceOptions = ref([]);
// è¡¨å•验证规则
const defectRules = reactive({
  deviceLedgerId: [{ required: true, message: '请输入设备名称', trigger: 'blur' }],
  defectDescription: [{ required: true, message: '请输入缺陷描述', trigger: 'blur' }]
});
// è¡¨å•引用
const defectFormRef = ref(null);
// å°è´¦åˆ—表
const ledgerList = ref([]);
const loadDeviceName = async () => {
  const { data } = await getDeviceLedger();
  // console.log(data);
  deviceOptions.value = data;
};
// èŽ·å–ç¼ºé™·åˆ—è¡¨
const fetchDefectList = async () => {
  try {
    const res = await getDefectList();
    if (res.code === 200) {
      defectList.value = res.data.records;
    } else {
      ElMessage.error(res.message || '获取缺陷列表失败');
    }
  } catch (error) {
    ElMessage.error('获取缺陷列表失败');
  }
};
// æäº¤ç¼ºé™·ç™»è®°è¡¨å•
const submitDefectForm = async () => {
  if (!defectFormRef.value) return;
  try {
    await defectFormRef.value.validate();
    const res = await registerDefect(defectForm);
    if (res.code === 200) {
      ElMessage.success('缺陷登记成功');
      showRegisterDialog.value = false;
      fetchDefectList();
    } else {
      ElMessage.error(res.message || '缺陷登记失败');
    }
  } catch (error) {
    ElMessage.error('请填写完整表单信息');
  }
};
// æ¶ˆé™¤ç¼ºé™·
const eliminateDefect = async (row) => {
  try {
    const res = await apiEliminateDefect(row);
    if (res.code === 200) {
      ElMessage.success('缺陷消除成功');
      fetchDefectList();
    } else {
      ElMessage.error(res.message || '缺陷消除失败');
    }
  } catch (error) {
    ElMessage.error('缺陷消除失败');
  }
};
// // è½¬ç»´ä¿®å·¥å•
// const transferToRepairOrder = async (id) => {
//   try {
//     const res = await transferToRepair(id);
//     if (res.code === 200) {
//       ElMessage.success('转维修工单成功');
//     } else {
//       ElMessage.error(res.message || '转维修工单失败');
//     }
//   } catch (error) {
//     ElMessage.error('转维修工单失败');
//   }
// };
// èŽ·å–ç¼ºé™·è®¾å¤‡å°è´¦
const getLedger = async (deviceLedgerId) => {
  try {
    const res = await getDefectLedger(deviceLedgerId);
    if (res.code === 200) {
      ledgerList.value = res.data.records;
      showLedgerDialog.value = true;
    } else {
      ElMessage.error(res.message || '获取缺陷设备台账失败');
    }
  } catch (error) {
    ElMessage.error('获取缺陷设备台账失败');
  }
};
// ç»„件挂载时获取缺陷列表
const onMounted = () => {
  fetchDefectList();
  loadDeviceName();
};
onMounted();
</script>
<style scoped>
.defect-management {
  padding: 20px;
}
.actions {
  margin-bottom: 10px;
}
</style>
src/views/equipmentManagement/operationManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,824 @@
<template>
  <div class="app-container">
    <!-- ç»Ÿè®¡æ¦‚览卡片 -->
    <div class="stats-overview">
      <el-row :gutter="20">
        <el-col :span="6">
          <el-card class="stats-card">
            <div class="stats-content">
              <div class="stats-icon running">
                <el-icon><VideoPlay /></el-icon>
              </div>
              <div class="stats-info">
                <div class="stats-value">{{ overviewData.runningDevices }}</div>
                <div class="stats-label">运行设备</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stats-card">
            <div class="stats-content">
              <div class="stats-icon stopped">
                <el-icon><VideoPause /></el-icon>
              </div>
              <div class="stats-info">
                <div class="stats-value">{{ overviewData.stoppedDevices }}</div>
                <div class="stats-label">停机设备</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stats-card">
            <div class="stats-content">
              <div class="stats-icon alarm">
                <el-icon><Warning /></el-icon>
              </div>
              <div class="stats-info">
                <div class="stats-value">{{ overviewData.alarmCount }}</div>
                <div class="stats-label">报警数量</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stats-card">
            <div class="stats-content">
              <div class="stats-icon maintenance">
                <el-icon><Tools /></el-icon>
              </div>
              <div class="stats-info">
                <div class="stats-value">{{ overviewData.maintenanceCount }}</div>
                <div class="stats-label">维护中</div>
              </div>
            </div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- ä¸»è¦å†…容区域 -->
    <el-row :gutter="20">
      <!-- å·¦ä¾§ï¼šè®¾å¤‡å¯åœè®°å½• -->
      <el-col :span="12">
        <el-card class="main-card">
          <template #header>
            <div class="card-header">
              <span>设备启停记录</span>
              <el-button type="primary" size="small" @click="refreshDeviceRecords">
                <el-icon><Refresh /></el-icon>
                åˆ·æ–°
              </el-button>
            </div>
          </template>
          <!-- è®¾å¤‡çŠ¶æ€ç­›é€‰ -->
          <div class="filter-section">
            <el-radio-group v-model="deviceFilter" @change="filterDeviceRecords">
              <el-radio-button label="all">全部</el-radio-button>
              <el-radio-button label="start">启动</el-radio-button>
              <el-radio-button label="stop">停机</el-radio-button>
            </el-radio-group>
          </div>
          <!-- è®¾å¤‡è®°å½•列表 -->
          <div class="device-records">
            <div
              v-for="record in filteredDeviceRecords"
              :key="record.id"
              class="device-record"
              :class="record.type"
            >
              <div class="record-icon">
                <el-icon v-if="record.type === 'start'"><VideoPlay /></el-icon>
                <el-icon v-else><VideoPause /></el-icon>
              </div>
              <div class="record-content">
                <div class="device-name">{{ record.deviceName }}</div>
                <div class="record-time">{{ record.time }}</div>
                <div class="record-status" :class="record.type">
                  {{ record.type === 'start' ? '设备启动' : '设备停机' }}
                </div>
              </div>
            </div>
          </div>
        </el-card>
      </el-col>
      <!-- å³ä¾§ï¼šè£…置开停工信息 -->
      <el-col :span="12">
        <el-card class="main-card">
          <template #header>
            <div class="card-header">
              <span>装置开停工信息</span>
              <el-button type="success" size="small" @click="refreshUnitInfo">
                <el-icon><Refresh /></el-icon>
                åˆ·æ–°
              </el-button>
            </div>
          </template>
          <!-- è£…置状态筛选 -->
          <div class="filter-section">
            <el-radio-group v-model="unitFilter" @change="filterUnitInfo">
              <el-radio-button label="all">全部</el-radio-button>
              <el-radio-button label="startup">开工</el-radio-button>
              <el-radio-button label="shutdown">停工</el-radio-button>
              <el-radio-button label="unplanned">非计划停工</el-radio-button>
            </el-radio-group>
          </div>
          <!-- è£…置信息列表 -->
          <div class="unit-info">
            <div
              v-for="unit in filteredUnitInfo"
              :key="unit.id"
              class="unit-item"
              :class="unit.status"
            >
              <div class="unit-header">
                <div class="unit-name">{{ unit.unitName }}</div>
                <div class="unit-status" :class="unit.status">
                  {{ getUnitStatusText(unit.status) }}
                </div>
              </div>
              <div class="unit-details">
                <div class="detail-item">
                  <span class="label">开始时间:</span>
                  <span class="value">{{ unit.startTime }}</span>
                </div>
                <div class="detail-item" v-if="unit.endTime">
                  <span class="label">结束时间:</span>
                  <span class="value">{{ unit.endTime }}</span>
                </div>
                <div class="detail-item" v-if="unit.reason">
                  <span class="label">原因:</span>
                  <span class="value">{{ unit.reason }}</span>
                </div>
              </div>
            </div>
          </div>
        </el-card>
      </el-col>
    </el-row>
    <!-- æŠ¥è­¦ä¿¡æ¯é›†ä¸­å±•示 -->
    <el-card class="alarm-card">
      <template #header>
        <div class="card-header">
          <span>报警信息集中展示</span>
          <div class="alarm-actions">
            <el-button type="warning" size="small" @click="refreshAlarms">
              <el-icon><Refresh /></el-icon>
              åˆ·æ–°
            </el-button>
            <el-button type="danger" size="small" @click="clearAlarms">
              <el-icon><Delete /></el-icon>
              æ¸…除已处理
            </el-button>
          </div>
        </div>
      </template>
      <!-- æŠ¥è­¦çº§åˆ«ç­›é€‰ -->
      <div class="filter-section">
        <el-radio-group v-model="alarmFilter" @change="filterAlarms">
          <el-radio-button label="all">全部</el-radio-button>
          <el-radio-button label="critical">严重</el-radio-button>
          <el-radio-button label="warning">警告</el-radio-button>
          <el-radio-button label="info">信息</el-radio-button>
        </el-radio-group>
      </div>
      <!-- æŠ¥è­¦ä¿¡æ¯è¡¨æ ¼ -->
      <el-table
        :data="filteredAlarms"
        style="width: 100%"
        :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
        max-height="400"
      >
        <el-table-column
          align="center"
          label="序号"
          type="index"
          width="60"
        />
        <el-table-column
          label="报警时间"
          prop="alarmTime"
          width="150"
          align="center"
        />
        <el-table-column
          label="设备名称"
          prop="deviceName"
          width="150"
          show-overflow-tooltip
        />
        <el-table-column
          label="报警级别"
          prop="level"
          width="100"
          align="center"
        >
          <template #default="scope">
            <el-tag
              :type="getAlarmTagType(scope.row.level)"
              size="small"
            >
              {{ scope.row.level }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column
          label="报警内容"
          prop="content"
          min-width="200"
          show-overflow-tooltip
        />
        <el-table-column
          label="处理状态"
          prop="status"
          width="100"
          align="center"
        >
          <template #default="scope">
            <el-tag
              :type="scope.row.status === '已处理' ? 'success' : 'danger'"
              size="small"
            >
              {{ scope.row.status }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column
          label="处理人"
          prop="handler"
          width="100"
          show-overflow-tooltip
        />
        <el-table-column
          label="操作"
          width="120"
          align="center"
        >
          <template #default="scope">
            <el-button
              v-if="scope.row.status === '未处理'"
              type="primary"
              size="small"
              @click="handleAlarm(scope.row)"
            >
              å¤„理
            </el-button>
            <el-button
              v-else
              type="info"
              size="small"
              disabled
            >
              å·²å¤„理
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
  VideoPlay,
  VideoPause,
  Warning,
  Tools,
  Refresh,
  Delete
} from '@element-plus/icons-vue'
// å“åº”式数据
const deviceFilter = ref('all')
const unitFilter = ref('all')
const alarmFilter = ref('all')
// æ¦‚览数据
const overviewData = reactive({
  runningDevices: 15,
  stoppedDevices: 3,
  alarmCount: 8,
  maintenanceCount: 2
})
// è®¾å¤‡å¯åœè®°å½•数据
const deviceRecords = ref([
  {
    id: 1,
    deviceName: '压缩机A-001',
    type: 'start',
    time: '2024-01-15 08:30:25'
  },
  {
    id: 2,
    deviceName: 'æ³µB-002',
    type: 'stop',
    time: '2024-01-15 08:25:10'
  },
  {
    id: 3,
    deviceName: '风机C-003',
    type: 'start',
    time: '2024-01-15 08:20:15'
  },
  {
    id: 4,
    deviceName: '搅拌器D-004',
    type: 'start',
    time: '2024-01-15 08:15:30'
  },
  {
    id: 5,
    deviceName: '加热器E-005',
    type: 'stop',
    time: '2024-01-15 08:10:45'
  },
  {
    id: 6,
    deviceName: '冷却器F-006',
    type: 'start',
    time: '2024-01-15 08:05:20'
  }
])
// è£…置开停工信息数据
const unitInfo = ref([
  {
    id: 1,
    unitName: '反应装置A',
    status: 'startup',
    startTime: '2024-01-15 08:00:00',
    endTime: null,
    reason: null
  },
  {
    id: 2,
    unitName: '分离装置B',
    status: 'shutdown',
    startTime: '2024-01-15 07:30:00',
    endTime: '2024-01-15 08:00:00',
    reason: '计划维护'
  },
  {
    id: 3,
    unitName: '精制装置C',
    status: 'unplanned',
    startTime: '2024-01-15 07:45:00',
    endTime: null,
    reason: '设备故障'
  },
  {
    id: 4,
    unitName: '包装装置D',
    status: 'startup',
    startTime: '2024-01-15 08:15:00',
    endTime: null,
    reason: null
  }
])
// æŠ¥è­¦ä¿¡æ¯æ•°æ®
const alarms = ref([
  {
    id: 1,
    alarmTime: '2024-01-15 08:30:00',
    deviceName: '压缩机A-001',
    level: '严重',
    content: '温度过高报警',
    status: '未处理',
    handler: ''
  },
  {
    id: 2,
    alarmTime: '2024-01-15 08:25:00',
    deviceName: 'æ³µB-002',
    level: '警告',
    content: '压力异常',
    status: '已处理',
    handler: '张三'
  },
  {
    id: 3,
    alarmTime: '2024-01-15 08:20:00',
    deviceName: '风机C-003',
    level: '信息',
    content: '运行时间达到维护周期',
    status: '未处理',
    handler: ''
  },
  {
    id: 4,
    alarmTime: '2024-01-15 08:15:00',
    deviceName: '搅拌器D-004',
    level: '严重',
    content: '振动异常',
    status: '未处理',
    handler: ''
  },
  {
    id: 5,
    alarmTime: '2024-01-15 08:10:00',
    deviceName: '加热器E-005',
    level: '警告',
    content: '加热效率下降',
    status: '已处理',
    handler: '李四'
  }
])
// è®¡ç®—属性 - è¿‡æ»¤åŽçš„设备记录
const filteredDeviceRecords = computed(() => {
  if (deviceFilter.value === 'all') {
    return deviceRecords.value
  }
  return deviceRecords.value.filter(record => record.type === deviceFilter.value)
})
// è®¡ç®—属性 - è¿‡æ»¤åŽçš„装置信息
const filteredUnitInfo = computed(() => {
  if (unitFilter.value === 'all') {
    return unitInfo.value
  }
  return unitInfo.value.filter(unit => unit.status === unitFilter.value)
})
// è®¡ç®—属性 - è¿‡æ»¤åŽçš„æŠ¥è­¦ä¿¡æ¯
const filteredAlarms = computed(() => {
  if (alarmFilter.value === 'all') {
    return alarms.value
  }
  return alarms.value.filter(alarm => alarm.level === alarmFilter.value)
})
// æ–¹æ³•
const refreshDeviceRecords = () => {
  ElMessage.success('设备记录已刷新')
  // è¿™é‡Œå¯ä»¥è°ƒç”¨API获取最新数据
}
const refreshUnitInfo = () => {
  ElMessage.success('装置信息已刷新')
  // è¿™é‡Œå¯ä»¥è°ƒç”¨API获取最新数据
}
const refreshAlarms = () => {
  ElMessage.success('报警信息已刷新')
  // è¿™é‡Œå¯ä»¥è°ƒç”¨API获取最新数据
}
const clearAlarms = async () => {
  try {
    await ElMessageBox.confirm('确定要清除所有已处理的报警信息吗?', '确认清除', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    alarms.value = alarms.value.filter(alarm => alarm.status === '未处理')
    ElMessage.success('已清除所有已处理的报警信息')
  } catch {
    // ç”¨æˆ·å–消操作
  }
}
const filterDeviceRecords = () => {
  // è¿‡æ»¤é€»è¾‘已在计算属性中处理
}
const filterUnitInfo = () => {
  // è¿‡æ»¤é€»è¾‘已在计算属性中处理
}
const filterAlarms = () => {
  // è¿‡æ»¤é€»è¾‘已在计算属性中处理
}
const getUnitStatusText = (status) => {
  const statusMap = {
    startup: '开工中',
    shutdown: '已停工',
    unplanned: '非计划停工'
  }
  return statusMap[status] || status
}
const getAlarmTagType = (level) => {
  const typeMap = {
    '严重': 'danger',
    '警告': 'warning',
    '信息': 'info'
  }
  return typeMap[level] || 'info'
}
const handleAlarm = (alarm) => {
  ElMessageBox.prompt('请输入处理说明', '处理报警', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    inputPlaceholder: '请输入处理说明'
  }).then(({ value }) => {
    alarm.status = '已处理'
    alarm.handler = '当前用户' // è¿™é‡Œåº”该获取当前登录用户
    ElMessage.success('报警处理完成')
  }).catch(() => {
    // ç”¨æˆ·å–消操作
  })
}
// ç»„件挂载时初始化数据
onMounted(() => {
  // è¿™é‡Œå¯ä»¥è°ƒç”¨API获取初始数据
  console.log('运行管理页面已加载')
})
</script>
<style scoped>
.app-container {
  padding: 20px;
  background: #f5f7fa;
  min-height: 100vh;
}
.stats-overview {
  margin-bottom: 20px;
}
.stats-card {
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.stats-content {
  display: flex;
  align-items: center;
  padding: 10px 0;
}
.stats-icon {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 15px;
  font-size: 24px;
  color: #fff;
}
.stats-icon.running {
  background: linear-gradient(135deg, #67C23A, #85CE61);
}
.stats-icon.stopped {
  background: linear-gradient(135deg, #F56C6C, #F78989);
}
.stats-icon.alarm {
  background: linear-gradient(135deg, #E6A23C, #EEBE77);
}
.stats-icon.maintenance {
  background: linear-gradient(135deg, #409EFF, #66B1FF);
}
.stats-info {
  flex: 1;
}
.stats-value {
  font-size: 24px;
  font-weight: bold;
  color: #333;
  line-height: 1;
  margin-bottom: 5px;
}
.stats-label {
  font-size: 14px;
  color: #666;
}
.main-card {
  margin-bottom: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.filter-section {
  margin-bottom: 15px;
}
.device-records {
  max-height: 400px;
  overflow-y: auto;
}
.device-record {
  display: flex;
  align-items: center;
  padding: 12px;
  margin-bottom: 8px;
  border-radius: 6px;
  background: #f8f9fa;
  border-left: 4px solid #ddd;
}
.device-record.start {
  border-left-color: #67C23A;
  background: #f0f9ff;
}
.device-record.stop {
  border-left-color: #F56C6C;
  background: #fef0f0;
}
.record-icon {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 12px;
  font-size: 16px;
  color: #fff;
}
.device-record.start .record-icon {
  background: #67C23A;
}
.device-record.stop .record-icon {
  background: #F56C6C;
}
.record-content {
  flex: 1;
}
.device-name {
  font-weight: 500;
  color: #333;
  margin-bottom: 4px;
}
.record-time {
  font-size: 12px;
  color: #666;
  margin-bottom: 4px;
}
.record-status {
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 12px;
  display: inline-block;
}
.record-status.start {
  background: #e1f3d8;
  color: #67C23A;
}
.record-status.stop {
  background: #fde2e2;
  color: #F56C6C;
}
.unit-info {
  max-height: 400px;
  overflow-y: auto;
}
.unit-item {
  padding: 15px;
  margin-bottom: 12px;
  border-radius: 6px;
  background: #f8f9fa;
  border-left: 4px solid #ddd;
}
.unit-item.startup {
  border-left-color: #67C23A;
  background: #f0f9ff;
}
.unit-item.shutdown {
  border-left-color: #409EFF;
  background: #f0f9ff;
}
.unit-item.unplanned {
  border-left-color: #E6A23C;
  background: #fdf6ec;
}
.unit-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}
.unit-name {
  font-weight: 500;
  color: #333;
  font-size: 14px;
}
.unit-status {
  font-size: 12px;
  padding: 4px 8px;
  border-radius: 12px;
  display: inline-block;
}
.unit-status.startup {
  background: #e1f3d8;
  color: #67C23A;
}
.unit-status.shutdown {
  background: #e1f3ff;
  color: #409EFF;
}
.unit-status.unplanned {
  background: #fdf6ec;
  color: #E6A23C;
}
.unit-details {
  font-size: 12px;
  color: #666;
}
.detail-item {
  margin-bottom: 4px;
}
.detail-item .label {
  font-weight: 500;
  margin-right: 4px;
}
.alarm-card {
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.alarm-actions {
  display: flex;
  gap: 8px;
}
:deep(.el-card__header) {
  background: #f8f9fa;
  border-bottom: 1px solid #e9ecef;
  font-weight: 500;
}
:deep(.el-table .el-table__header-wrapper th) {
  background-color: #F0F1F5 !important;
  color: #333333;
  font-weight: 600;
}
:deep(.el-table .el-table__body-wrapper td) {
  padding: 8px 0;
}
:deep(.el-radio-button__inner) {
  border-radius: 4px;
}
:deep(.el-radio-button:first-child .el-radio-button__inner) {
  border-left: 1px solid #dcdfe6;
  border-radius: 4px;
}
:deep(.el-radio-button:last-child .el-radio-button__inner) {
  border-radius: 4px;
}
</style>
src/views/equipmentManagement/spareParts/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,417 @@
<template>
  <div class="spare-part-category">
    <div class="table_list">
      <div class="actions">
        <el-text class="mx-1" size="large">设备分类</el-text>
        <div>
          <el-button @click="fetchTreeData" :loading="loading">刷新</el-button>
          <el-button type="primary" @click="addCategory" >新增</el-button>
        </div>
      </div>
      <el-table
        v-loading="loading"
        :data="renderTableData"
        style="width: 100%; margin-top: 10px;"
        border
        row-key="id"
        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
      >
        <el-table-column prop="name" label="分类名称" width="450">
          <template #default="{ row }">
            <span :style="{ paddingLeft: getIndentation(row) + 'px' }">
              {{ row.name }}
            </span>
          </template>
        </el-table-column>
        <el-table-column prop="sparePartsNo" label="分类编号" width="200"></el-table-column>
        <el-table-column prop="status" label="状态" width="100">
          <template #default="{ row }">
            <el-tag type="success" size="small">{{ row.status }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="description" label="描述" win-width="330"></el-table-column>
        <el-table-column label="操作" width="180" fixed="right">
          <template #default="{ row }">
            <el-button
              type="text"
              size="small"
              @click="() => editCategory(row)"
              :disabled="loading"
            >
              ç¼–辑
            </el-button>
            <el-button
              type="text"
              size="small"
              @click="() => deleteCategory(row.id)"
              style="color: #f56c6c;"
              :disabled="loading"
            >
              åˆ é™¤
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <el-dialog title="分类管理" v-model="dialogVisible" width="60%">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
        <el-form-item label="分类名称" prop="name">
          <el-input v-model="form.name"></el-input>
        </el-form-item>
        <el-form-item label="分类编号" prop="sparePartsNo">
          <el-input v-model="form.sparePartsNo"></el-input>
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-select v-model="form.status" placeholder="请选择状态">
            <el-option label="正常" value="正常"></el-option>
            <el-option label="禁用" value="禁用"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="描述" prop="description">
          <el-input v-model="form.description"></el-input>
        </el-form-item>
        <el-form-item label="上级分类" prop="parentId">
          <el-select v-model="form.parentId" placeholder="请选择上级分类">
            <el-option label="无上级分类" :value="null"></el-option>
            <el-option
              v-for="(item, index) in categories"
              :key="index"
              :label="item.name"
              :value="item.id"
            ></el-option>
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false" :disabled="formLoading">取消</el-button>
          <el-button type="primary" @click="submitForm" :loading="formLoading">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, computed, onMounted, reactive, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { getSparePartsList, addSparePart, editSparePart, delSparePart,getSparePartsTree } from "@/api/equipmentManagement/spareParts";
// åŠ è½½çŠ¶æ€
const loading = ref(false);
const formLoading = ref(false);
// å¯¹è¯æ¡†æ˜¾ç¤ºçŠ¶æ€
const dialogVisible = ref(false);
// ç¼–辑 ID
const editId = ref(null);
// è¡¨æ ¼æ•°æ®
const categories = ref([]);
// æ¸²æŸ“用的表格数据
// const renderTableData = computed(() => buildTree(categories.value));
const renderTableData = ref([]);
const operationType = ref('add')
// è¡¨å•引用
const formRef = ref(null);
// è¡¨å•数据
const form = reactive({
  id:'',
  name: '',
  sparePartsNo: '',
  status: '',
  description: '',
  parentId: null
});
// è¡¨å•验证规则
const rules = reactive({
  name: [
    { required: true, message: '请输入分类名称', trigger: 'blur' }
  ],
  sparePartsNo: [
    { required: true, message: '请输入分类编号', trigger: 'blur' }
  ],
  status: [
    { required: true, message: '请选择状态', trigger: 'change' }
  ]
});
// èŽ·å–ç¼©è¿›é‡
const getIndentation = (row) => {
  // è¿™é‡Œç®€å•返回 20,可根据实际需求实现层级缩进逻辑
  return 20;
};
// å®šä¹‰ buildTree å‡½æ•°
const buildTree = (flatData) => {
  const map = {};
  const result = [];
  if(flatData){
    return result;
  }
  flatData.forEach(item => {
    map[item.id] = { ...item, children: [] };
  });
  flatData.forEach(item => {
    if (item.parentId === null || !map[item.parentId]) {
      result.push(map[item.id]);
    } else {
      map[item.parentId].children.push(map[item.id]);
    }
  });
  return result;
};
//获取树形结构
const fetchTreeData = async () => {
  fetchCategories();
  try {
    const res = await getSparePartsTree();
    if (res.code === 200) {
      renderTableData.value = res.data;
    } else {
      ElMessage.error(res.message || '获取分类列表失败');
    }
  }catch (error) {
    ElMessage.error('获取分类列表失败');
  }
}
// èŽ·å–åˆ†ç±»åˆ—è¡¨
const fetchCategories = async () => {
  loading.value = true;
  try {
    const res = await getSparePartsList();
    if (res.code === 200) {
      categories.value = res.data.records;
    } else {
      ElMessage.error(res.message || '获取分类列表失败');
    }
  } catch (error) {
    ElMessage.error('获取分类列表失败');
  } finally {
    loading.value = false;
  }
};
// æ–°å¢žåˆ†ç±»
const addCategory = () => {
  form.id = '';
  form.name = '';
  form.sparePartsNo = '';
  form.status = '';
  form.description = '';
  form.parentId = null;
  operationType.value = 'add'
  dialogVisible.value = true;
  console.log('dialogVisible æ›´æ–°ä¸º', dialogVisible.value);
};
// ç¼–辑分类
const editCategory = (row) => {
  Object.assign(form, row);
  operationType.value = 'edit'
  dialogVisible.value = true;
};
// åˆ é™¤åˆ†ç±»
const deleteCategory = async (id) => {
  try {
    await ElMessageBox.confirm('此操作将永久删除该分类,是否继续?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    });
    loading.value = true;
    const res = await delSparePart(id);
    if (res.code === 200) {
      ElMessage.success('删除成功');
      fetchTreeData();
    } else {
      ElMessage.error(res.message || '删除失败');
    }
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('删除失败');
    }
  } finally {
    loading.value = false;
  }
};
// æäº¤è¡¨å•
const submitForm = async () => {
  if (!formRef.value) return;
  try {
    await formRef.value.validate();
    formLoading.value = true;
    if (operationType.value === 'edit') {
      let res = await editSparePart(form);
      if (res.code === 200) {
      ElMessage.success('编辑成功');
      dialogVisible.value = false;
      fetchTreeData();
    }
    } else {
      let res = await addSparePart(form);
        if (res.code === 200) {
        ElMessage.success('编辑成功');
        dialogVisible.value = false;
        fetchTreeData();
      }
    }
  } catch (error) {
    ElMessage.error('请填写完整表单信息');
  } finally {
    formLoading.value = false;
  }
};
// ç»„件挂载时获取分类列表
onMounted(() => {
  fetchCategories();
  fetchTreeData();
});
</script>
<style scoped>
.spare-part-category {
  padding: 20px;
}
.table_list {
  margin-top: unset;
}
.actions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
  align-items: center;
}
/* åµŒå¥—树形结构样式 */
.nested-tree-node {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 0 4px;
  height: 30px;
  line-height: 30px;
}
.category-code {
  color: #606266;
  font-size: 12px;
  margin-left: 8px;
}
.tree-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}
/* è¡¨æ ¼æ ·å¼è°ƒæ•´ */
.el-table {
  font-size: 14px;
}
.el-table__header-wrapper th {
  background-color: #f5f7fa;
  font-weight: 600;
}
.el-table__row:hover > td {
  background-color: #fafafa;
}
/* åµŒå¥—树形结构特定样式 */
.nested-tree {
  background-color: transparent;
}
.nested-tree .el-tree-node__content {
  height: auto;
  background-color: transparent;
}
/* æœç´¢æ¡†æ ·å¼è°ƒæ•´ */
.actions .el-input {
  margin-right: 10px;
  width: 200px;
}
/* æŒ‰é’®ç»„样式 */
.actions > div {
  display: flex;
  gap: 10px;
}
/* ç¡®ä¿è¡¨æ ¼ä¸­çš„æ“ä½œæŒ‰é’®ä¸ä¼šè¢«æˆªæ–­ */
.el-table-column--fixed-right .el-button {
  margin: 0 2px;
}
/* æ ‘形节点内容样式 */
.nested-tree .el-tree-node__expand-icon {
  font-size: 12px;
  margin-right: 4px;
}
/* ç©ºçŠ¶æ€æ ·å¼ */
.el-table .cell:empty::before {
  content: '-';
  color: #c0c4cc;
}
/* å±•å¼€/折叠功能样式 */
.expand-icon {
  display: inline-block;
  width: 20px;
  height: 20px;
  text-align: center;
  line-height: 20px;
  cursor: pointer;
  font-size: 12px;
  color: #909399;
}
.expand-icon.expanded {
  color: #409eff;
}
/* å±•开内容样式 */
.expand-content {
  padding: 10px 20px;
}
/* å­çº§å†…容样式 */
.child-content {
  padding-left: 40px;
}
/* æ— å­åˆ†ç±»æç¤ºæ ·å¼ */
.no-children {
  padding: 10px 20px;
  color: #909399;
  font-size: 14px;
}
/* ç¡®ä¿å±•开的表格样式正确 */
.el-table .el-table__expanded-cell {
  background-color: #fafafa;
}
/* å±•开的子表格样式 */
.el-table .el-table {
  border-top: none;
  border-bottom: none;
}
/* å±•开的子表格单元格样式 */
.expand-content .el-table__body-wrapper .el-table__row {
  background-color: #ffffff;
}
.expand-content .el-table__body-wrapper .el-table__row:hover > td {
  background-color: #fafafa;
}
</style>
src/views/inventoryManagement/stockManagement/index.vue
@@ -115,7 +115,6 @@
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="出库人:" prop="entryPerson">
              <el-select v-model="form.createUser" placeholder="请选择" clearable>
@@ -123,6 +122,11 @@
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
          <el-form-item label="最低库存:" prop="minStock">
            <el-input v-model="form.minStock" placeholder="请输入最低库存" clearable />
          </el-form-item>
        </el-col>
        </el-row>
      </el-form>
      <template #footer>
@@ -149,7 +153,8 @@
  exportStockManage
} from "@/api/inventoryManagement/stockManage.js";
import {
  updateManagement
  updateManagement,
} from "@/api/inventoryManagement/stockIn.js";
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
@@ -194,6 +199,7 @@
    inboundBatch: '',
    stockQuantity: '',
    boundTime: '',
    minStock: '', // æ–°å¢žæœ€ä½Žåº“存字段
  },
  rules: {
    supplierName: [{ required: true, message: '请输入供应商名称', trigger: 'blur' }],
@@ -207,7 +213,8 @@
    taxExclusiveTotalPrice: [{ required: true, message: '请输入不含税总价', trigger: 'blur' }],
    boundTime: [{ required: true, message: '请选择库存时间', trigger: 'change' }],
    inboundTime: [{ required: true, message: '请选择入库时间', trigger: 'change' }],
    inboundPerson: [{ required: true, message: '请选择出库人', trigger: 'change' }]
    inboundPerson: [{ required: true, message: '请选择出库人', trigger: 'change' }],
    minStock: [{ required: true, message: '请输入最低库存', trigger: 'blur' }],
  }
})
const { searchForm, form, rules } = toRefs(data)
@@ -310,10 +317,21 @@
        proxy.$modal.msgSuccess("提交成功")
        closeDia()
        getList()
        // æäº¤åŽæ£€æŸ¥åº“存并尝试创建请购单
        checkStockAndAutoCreatePurchase();
      })
    }
  })
}
// æ£€æŸ¥åº“存并自动创建请购单
const checkStockAndAutoCreatePurchase = async () => {
  try {
    await checkStockAndCreatePurchase();
  } catch (error) {
    console.error('自动补货失败:', error);
    proxy.$modal.msgError('自动补货失败,请手动处理');
  }
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef")
@@ -376,6 +394,7 @@
}
onMounted(() => {
  getList()
  checkStockAndAutoCreatePurchase();
})
</script>
src/views/inventoryManagement/stockReport/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,678 @@
<template>
  <div class="app-container">
    <!-- æœç´¢è¡¨å• -->
    <div class="search_form">
      <div class="search_left">
        <span class="search_title">报表类型:</span>
        <el-select
          v-model="searchForm.reportType"
          style="width: 150px;"
          placeholder="请选择"
          @change="handleReportTypeChange"
        >
          <el-option label="日报" value="daily" />
          <el-option label="月报" value="monthly" />
          <el-option label="作业报表" value="work" />
          <el-option label="进出存报表" value="inout" />
        </el-select>
        <span class="search_title ml10">时间范围:</span>
         <el-date-picker
           v-if="searchForm.reportType === 'daily'"
           v-model="searchForm.singleDate"
           type="date"
           placeholder="请选择日期"
           format="YYYY-MM-DD"
           value-format="YYYY-MM-DD"
           style="width: 200px;"
         />
        <el-date-picker
          v-else-if="searchForm.reportType === 'monthly'"
          v-model="searchForm.monthRange"
          type="monthrange"
          range-separator="至"
          start-placeholder="开始月份"
          end-placeholder="结束月份"
          format="YYYY-MM"
          value-format="YYYY-MM"
          style="width: 240px;"
        />
        <el-date-picker
          v-else
          v-model="searchForm.dateRange"
          type="daterange"
          range-separator="至"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          format="YYYY-MM-DD"
          value-format="YYYY-MM-DD"
          style="width: 240px;"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">
          æŸ¥è¯¢
        </el-button>
        <el-button @click="handleReset">重置</el-button>
      </div>
      <div class="search_right">
        <el-button type="success" @click="handleExport" icon="Download">
          å¯¼å‡ºæŠ¥è¡¨
        </el-button>
      </div>
    </div>
    <!-- ç»Ÿè®¡å¡ç‰‡ -->
    <div class="stats_cards" v-if="reportData.summary">
      <el-row :gutter="20">
        <el-col :span="6">
          <el-card class="stats_card">
            <div class="stats_content">
              <div class="stats_icon in">
                <el-icon><TrendCharts /></el-icon>
              </div>
              <div class="stats_info">
                <div class="stats_value">{{ reportData.summary.totalIn || 0 }}</div>
                <div class="stats_label">总入库量</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stats_card">
            <div class="stats_content">
              <div class="stats_icon out">
                <el-icon><TrendCharts /></el-icon>
              </div>
              <div class="stats_info">
                <div class="stats_value">{{ reportData.summary.totalOut || 0 }}</div>
                <div class="stats_label">总出库量</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stats_card">
            <div class="stats_content">
              <div class="stats_icon stock">
                <el-icon><Box /></el-icon>
              </div>
              <div class="stats_info">
                <div class="stats_value">{{ reportData.summary.currentStock || 0 }}</div>
                <div class="stats_label">当前库存</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stats_card">
            <div class="stats_content">
              <div class="stats_icon turnover">
                <el-icon><Refresh /></el-icon>
              </div>
              <div class="stats_info">
                <div class="stats_value">{{ reportData.summary.turnoverRate || 0 }}%</div>
                <div class="stats_label">周转率</div>
              </div>
            </div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- å›¾è¡¨åŒºåŸŸ -->
    <div class="chart_section" v-if="reportData.chartData">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-card>
            <template #header>
              <span>库存趋势图</span>
            </template>
            <div ref="trendChart" style="height: 300px;"></div>
          </el-card>
        </el-col>
        <el-col :span="12">
          <el-card>
            <template #header>
              <span>进出库对比</span>
            </template>
            <div ref="comparisonChart" style="height: 300px;"></div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- è¯¦ç»†æ•°æ®è¡¨æ ¼ -->
    <div class="table_section">
      <el-card>
        <template #header>
          <span>{{ getTableTitle() }}</span>
        </template>
         <el-table
           v-loading="tableLoading"
           :data="reportData.tableData"
           border
           height="400"
           style="width: 100%"
           :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
         >
          <el-table-column
            align="center"
            label="序号"
            type="index"
            width="60"
          />
           <el-table-column
             v-if="searchForm.reportType === 'daily'"
             label="日期"
             prop="date"
             width="100"
             align="center"
           />
           <el-table-column
             v-if="searchForm.reportType === 'monthly'"
             label="月份"
             prop="month"
             width="100"
             align="center"
           />
           <el-table-column
             label="入库时间"
             prop="createTime"
             width="100"
             show-overflow-tooltip
           />
           <el-table-column
             label="入库批次"
             prop="inboundBatches"
             width="160"
             show-overflow-tooltip
           />
           <el-table-column
             label="供应商名称"
             prop="supplierName"
             min-width="240"
             show-overflow-tooltip
           />
           <el-table-column
             label="产品大类"
             prop="productCategory"
             width="100"
             show-overflow-tooltip
           />
           <el-table-column
             label="规格型号"
             prop="specificationModel"
             min-width="200"
             show-overflow-tooltip
           />
           <el-table-column
             label="单位"
             prop="unit"
             width="70"
             show-overflow-tooltip
           />
           <el-table-column
             label="期初库存"
             prop="beginStock"
             width="100"
             align="center"
           />
           <el-table-column
             label="入库数量"
             prop="inboundNum"
             width="100"
             align="center"
           />
           <el-table-column
             label="出库数量"
             prop="outboundNum"
             width="100"
             align="center"
           />
           <el-table-column
             label="期末库存"
             prop="endStock"
             width="100"
             align="center"
           />
           <el-table-column
             label="含税单价"
             prop="taxInclusiveUnitPrice"
             width="100"
             show-overflow-tooltip
           />
           <el-table-column
             label="含税总价"
             prop="taxInclusiveTotalPrice"
             width="100"
             show-overflow-tooltip
           />
           <el-table-column
             label="税率(%)"
             prop="taxRate"
             width="80"
             show-overflow-tooltip
           />
           <el-table-column
             label="不含税总价"
             prop="taxExclusiveTotalPrice"
             width="100"
             show-overflow-tooltip
           />
           <el-table-column
             label="入库人"
             prop="createBy"
             width="80"
             show-overflow-tooltip
           />
           <el-table-column
             v-if="searchForm.reportType === 'work'"
             label="操作人员"
             prop="operator"
             width="80"
             align="center"
           />
           <el-table-column
             v-if="searchForm.reportType === 'work'"
             label="操作时间"
             prop="operateTime"
             width="150"
             align="center"
           />
        </el-table>
      </el-card>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import * as echarts from 'echarts'
import {
  getStockDailyReport,
  getStockMonthlyReport,
  getWorkReport,
  getStockInOutReport,
  exportStockReport
} from '@/api/inventoryManagement/stockReport'
// å“åº”式数据
const tableLoading = ref(false)
const trendChart = ref(null)
const comparisonChart = ref(null)
const searchForm = reactive({
  reportType: 'daily',
  singleDate: '',
  dateRange: [],
  monthRange: []
})
const reportData = ref({
  summary: null,
  chartData: null,
  tableData: []
})
// èŽ·å–è¡¨æ ¼æ ‡é¢˜
const getTableTitle = () => {
  const typeMap = {
    daily: '日报详细数据',
    monthly: '月报详细数据',
    work: '作业报表详细数据',
    inout: '进出存报表详细数据'
  }
  return typeMap[searchForm.reportType] || '报表详细数据'
}
// æŠ¥è¡¨ç±»åž‹æ”¹å˜
const handleReportTypeChange = () => {
  reportData.value = {
    summary: null,
    chartData: null,
    tableData: []
  }
}
// æŸ¥è¯¢æ•°æ®
const handleQuery = async () => {
  if (!validateSearchForm()) {
    return
  }
  tableLoading.value = true
  try {
    const params = getQueryParams()
    let response
    switch (searchForm.reportType) {
      case 'daily':
        response = await getStockDailyReport(params)
        break
      case 'monthly':
        response = await getStockMonthlyReport(params)
        break
      case 'work':
        response = await getWorkReport(params)
        break
      case 'inout':
        response = await getStockInOutReport(params)
        break
      default:
        throw new Error('未知的报表类型')
    }
    if (response.code === 200) {
      reportData.value = response.data
      nextTick(() => {
        initCharts()
      })
    }
  } catch (error) {
    ElMessage.error('查询失败:' + error.message)
  } finally {
    tableLoading.value = false
  }
}
// éªŒè¯æœç´¢è¡¨å•
const validateSearchForm = () => {
  if (searchForm.reportType === 'daily') {
    if (!searchForm.singleDate) {
      ElMessage.warning('请选择日期')
      return false
    }
  } else if (searchForm.reportType === 'work' || searchForm.reportType === 'inout') {
    if (!searchForm.dateRange || searchForm.dateRange.length !== 2) {
      ElMessage.warning('请选择日期范围')
      return false
    }
  } else if (searchForm.reportType === 'monthly') {
    if (!searchForm.monthRange || searchForm.monthRange.length !== 2) {
      ElMessage.warning('请选择月份范围')
      return false
    }
  }
  return true
}
// èŽ·å–æŸ¥è¯¢å‚æ•°
const getQueryParams = () => {
  const params = {
    reportType: searchForm.reportType
  }
  if (searchForm.reportType === 'daily') {
    params.reportDate = searchForm.singleDate
  } else if (searchForm.reportType === 'monthly') {
    params.startMonth = searchForm.monthRange[0]
    params.endMonth = searchForm.monthRange[1]
  } else {
    params.startDate = searchForm.dateRange[0]
    params.endDate = searchForm.dateRange[1]
  }
  return params
}
// é‡ç½®æœç´¢
const handleReset = () => {
  searchForm.reportType = 'daily'
  searchForm.singleDate = ''
  searchForm.dateRange = []
  searchForm.monthRange = []
  reportData.value = {
    summary: null,
    chartData: null,
    tableData: []
  }
}
// å¯¼å‡ºæŠ¥è¡¨
const handleExport = async () => {
  if (!validateSearchForm()) {
    return
  }
  try {
    const params = getQueryParams()
    const response = await exportStockReport(params)
    // åˆ›å»ºä¸‹è½½é“¾æŽ¥
    const blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
    const url = window.URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.href = url
    link.download = `${getTableTitle()}_${new Date().getTime()}.xlsx`
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
    window.URL.revokeObjectURL(url)
    ElMessage.success('导出成功')
  } catch (error) {
    ElMessage.error('导出失败:' + error.message)
  }
}
// åˆå§‹åŒ–图表
const initCharts = () => {
  if (!reportData.value.chartData) return
  initTrendChart()
  initComparisonChart()
}
// åˆå§‹åŒ–趋势图
const initTrendChart = () => {
  if (!trendChart.value) return
  const chart = echarts.init(trendChart.value)
  const option = {
    title: {
      text: '库存变化趋势',
      left: 'center'
    },
    tooltip: {
      trigger: 'axis'
    },
    legend: {
      data: ['库存量'],
      top: 30
    },
    xAxis: {
      type: 'category',
      data: reportData.value.chartData.trendDates || []
    },
    yAxis: {
      type: 'value'
    },
    series: [{
      name: '库存量',
      type: 'line',
      data: reportData.value.chartData.trendValues || [],
      smooth: true,
      itemStyle: {
        color: '#409EFF'
      }
    }]
  }
  chart.setOption(option)
}
// åˆå§‹åŒ–对比图
const initComparisonChart = () => {
  if (!comparisonChart.value) return
  const chart = echarts.init(comparisonChart.value)
  const option = {
    title: {
      text: '进出库对比',
      left: 'center'
    },
    tooltip: {
      trigger: 'axis'
    },
    legend: {
      data: ['入库', '出库'],
      top: 30
    },
    xAxis: {
      type: 'category',
      data: reportData.value.chartData.comparisonDates || []
    },
    yAxis: {
      type: 'value'
    },
    series: [
      {
        name: '入库',
        type: 'bar',
        data: reportData.value.chartData.inValues || [],
        itemStyle: {
          color: '#67C23A'
        }
      },
      {
        name: '出库',
        type: 'bar',
        data: reportData.value.chartData.outValues || [],
        itemStyle: {
          color: '#F56C6C'
        }
      }
    ]
  }
  chart.setOption(option)
}
// ç»„件挂载时设置默认时间
onMounted(() => {
  const today = new Date()
  searchForm.singleDate = today.toISOString().split('T')[0]
  const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000)
  searchForm.dateRange = [
    yesterday.toISOString().split('T')[0],
    today.toISOString().split('T')[0]
  ]
})
</script>
<style scoped>
.app-container {
  padding: 20px;
}
.search_form {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding: 20px;
  background: #fff;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.search_left {
  display: flex;
  align-items: center;
}
.search_title {
  font-weight: 500;
  color: #333;
  margin-right: 8px;
}
.ml10 {
  margin-left: 10px;
}
.stats_cards {
  margin-bottom: 20px;
}
.stats_card {
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.stats_content {
  display: flex;
  align-items: center;
  padding: 10px 0;
}
.stats_icon {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 15px;
  font-size: 24px;
  color: #fff;
}
.stats_icon.in {
  background: linear-gradient(135deg, #67C23A, #85CE61);
}
.stats_icon.out {
  background: linear-gradient(135deg, #F56C6C, #F78989);
}
.stats_icon.stock {
  background: linear-gradient(135deg, #409EFF, #66B1FF);
}
.stats_icon.turnover {
  background: linear-gradient(135deg, #E6A23C, #EEBE77);
}
.stats_info {
  flex: 1;
}
.stats_value {
  font-size: 24px;
  font-weight: bold;
  color: #333;
  line-height: 1;
  margin-bottom: 5px;
}
.stats_label {
  font-size: 14px;
  color: #666;
}
.chart_section {
  margin-bottom: 20px;
}
.table_section {
  margin-bottom: 20px;
}
:deep(.el-card__header) {
  background: #f8f9fa;
  border-bottom: 1px solid #e9ecef;
  font-weight: 500;
}
:deep(.el-table .el-table__header-wrapper th) {
  background-color: #F0F1F5 !important;
  color: #333333;
  font-weight: 600;
}
:deep(.el-table .el-table__body-wrapper td) {
  padding: 8px 0;
}
</style>