Merge remote-tracking branch 'origin/dev' into dev
| | |
| | | # 页颿 é¢
|
| | | VITE_APP_TITLE = æµè¯è¿éå管çç³»ç»
|
| | | VITE_APP_TITLE = ä¸å°ä¼ä¸æ°åå转åäºçº§å¥é¤å
|
| | |
|
| | | # å¼åç¯å¢é
ç½®
|
| | | VITE_APP_ENV = 'development'
|
| | |
|
| | | # æµè¯è¿éå管çç³»ç»/å¼åç¯å¢
|
| | | # ä¸å°ä¼ä¸æ°åå转åäºçº§å¥é¤å
/å¼åç¯å¢
|
| | | VITE_APP_BASE_API = '/dev-api'
|
| | |
| | | # 页颿 é¢
|
| | | VITE_APP_TITLE = æµè¯è¿éå管çç³»ç»
|
| | | VITE_APP_TITLE = ä¸å°ä¼ä¸æ°åå转åäºçº§å¥é¤å
|
| | |
|
| | | # ç产ç¯å¢é
ç½®
|
| | | VITE_APP_ENV = 'production'
|
| | |
|
| | | # æµè¯è¿éå管çç³»ç»/ç产ç¯å¢
|
| | | # ä¸å°ä¼ä¸æ°åå转åäºçº§å¥é¤å
/ç产ç¯å¢
|
| | | VITE_APP_BASE_API = '/prod-api'
|
| | |
|
| | | # æ¯å¦å¨æå
æ¶å¼å¯åç¼©ï¼æ¯æ gzip å brotli
|
| | |
| | | # 页颿 é¢
|
| | | VITE_APP_TITLE = æµè¯è¿éå管çç³»ç»
|
| | | VITE_APP_TITLE = ä¸å°ä¼ä¸æ°åå转åäºçº§å¥é¤å
|
| | |
|
| | | # ç产ç¯å¢é
ç½®
|
| | | VITE_APP_ENV = 'staging'
|
| | |
|
| | | # æµè¯è¿éå管çç³»ç»/ç产ç¯å¢
|
| | | # ä¸å°ä¼ä¸æ°åå转åäºçº§å¥é¤å
/ç产ç¯å¢
|
| | | VITE_APP_BASE_API = '/stage-api'
|
| | |
|
| | | # æ¯å¦å¨æå
æ¶å¼å¯åç¼©ï¼æ¯æ gzip å brotli
|
| | |
| | | }, |
| | | "screen": "screen/HYSNView.png", |
| | | "logo": "logo/LCLogo.png", |
| | | "favicon": "favicon/BHMY.ico" |
| | | "favicon": "favicon/favicon.ico" |
| | | }, |
| | | "WDSY": { |
| | | "env": { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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' |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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' |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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", |
| | | |
| | | }); |
| | | }; |
| | | |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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, |
| | | }); |
| | | }; |
| | |
| | | <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>
|
| | |
| | | ], |
| | | }, |
| | | { |
| | | 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, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | |
| | | 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 = { |
| | |
| | | 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(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | |
| | | <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" /> |
| | |
| | | <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> |
| | | |
| | |
| | | </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 /> |
| | |
| | | </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"> |
| | |
| | | </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="请è¾å
¥å¶åº¦ç¼å·" /> |
| | |
| | | <el-button type="primary" @click="submitRegulation">åå¸å¶åº¦</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </el-dialog> --> |
| | | |
| | | <!-- ç¨å°è¯¦æ
å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="showSealDetailDialog" title="ç¨å°ç³è¯·è¯¦æ
" width="700px"> |
| | |
| | | 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 operationType = ref('add') |
| | | const tableData = ref([]) |
| | | // ç¨å°ç³è¯·ç¸å
³ |
| | | const userStore = useUserStore() |
| | | const showSealApplyDialog = ref(false) |
| | | const tableLoading = ref(false) |
| | | const showSealDetailDialog = ref(false) |
| | |
| | | }) |
| | | } |
| | | |
| | | |
| | | |
| | | // è·åå°ç« ç³è¯·åè¡¨æ°æ® |
| | | 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 => { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | |
| | | </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> |
| | |
| | | </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> |
| | |
| | | exportStockManage |
| | | } from "@/api/inventoryManagement/stockManage.js"; |
| | | import { |
| | | updateManagement |
| | | updateManagement, |
| | | |
| | | } from "@/api/inventoryManagement/stockIn.js"; |
| | | const userStore = useUserStore() |
| | | const { proxy } = getCurrentInstance() |
| | |
| | | inboundBatch: '', |
| | | stockQuantity: '', |
| | | boundTime: '', |
| | | minStock: '', // æ°å¢æä½åºååæ®µ |
| | | }, |
| | | rules: { |
| | | supplierName: [{ required: true, message: '请è¾å
¥ä¾åºååç§°', trigger: 'blur' }], |
| | |
| | | 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) |
| | |
| | | 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") |
| | |
| | | } |
| | | onMounted(() => { |
| | | getList() |
| | | checkStockAndAutoCreatePurchase(); |
| | | }) |
| | | </script> |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |