Merge branch 'dev_7004' into dev_tide
# Conflicts:
# package.json
# src/router/index.js
# vite.config.js
| | |
| | | # 页颿 é¢
|
| | | VITE_APP_TITLE = MISç³»ç»ï¼ç®¡çä¿¡æ¯ç³»ç»ï¼
|
| | | VITE_APP_TITLE = è¯å¯¼äºï¼ç®¡çä¿¡æ¯ç³»ç»ï¼
|
| | |
|
| | | # å¼åç¯å¢é
ç½®
|
| | | VITE_APP_ENV = 'development'
|
| | |
|
| | | # MISç³»ç»ï¼ç®¡çä¿¡æ¯ç³»ç»ï¼/å¼åç¯å¢
|
| | | # è¯å¯¼äºï¼ç®¡çä¿¡æ¯ç³»ç»ï¼/å¼åç¯å¢
|
| | | VITE_APP_BASE_API = '/dev-api'
|
| | |
| | | # 页颿 é¢
|
| | | VITE_APP_TITLE = MISç³»ç»ï¼ç®¡çä¿¡æ¯ç³»ç»ï¼
|
| | | VITE_APP_TITLE = è¯å¯¼äºï¼ç®¡çä¿¡æ¯ç³»ç»ï¼
|
| | |
|
| | | # ç产ç¯å¢é
ç½®
|
| | | VITE_APP_ENV = 'production'
|
| | |
|
| | | # MISç³»ç»ï¼ç®¡çä¿¡æ¯ç³»ç»ï¼/ç产ç¯å¢
|
| | | # è¯å¯¼äºï¼ç®¡çä¿¡æ¯ç³»ç»ï¼/ç产ç¯å¢
|
| | | VITE_APP_BASE_API = '/prod-api'
|
| | |
|
| | | # æ¯å¦å¨æå
æ¶å¼å¯åç¼©ï¼æ¯æ gzip å brotli
|
| | |
| | | # 页颿 é¢
|
| | | VITE_APP_TITLE = MISç³»ç»ï¼ç®¡çä¿¡æ¯ç³»ç»ï¼
|
| | | VITE_APP_TITLE = è¯å¯¼äºï¼ç®¡çä¿¡æ¯ç³»ç»ï¼
|
| | |
|
| | | # ç产ç¯å¢é
ç½®
|
| | | VITE_APP_ENV = 'staging'
|
| | |
|
| | | # MISç³»ç»ï¼ç®¡çä¿¡æ¯ç³»ç»ï¼/ç产ç¯å¢
|
| | | # è¯å¯¼äºï¼ç®¡çä¿¡æ¯ç³»ç»ï¼/ç产ç¯å¢
|
| | | VITE_APP_BASE_API = '/stage-api'
|
| | |
|
| | | # æ¯å¦å¨æå
æ¶å¼å¯åç¼©ï¼æ¯æ gzip å brotli
|
| | |
| | | @echo off
|
| | | echo.
|
| | | echo [ä¿¡æ¯] æå
Webå·¥ç¨ï¼çædistæä»¶ã
|
| | | echo.
|
| | |
|
| | | %~d0
|
| | | cd %~dp0
|
| | |
|
| | | cd ..
|
| | | yarn build:prod
|
| | |
|
| | | @echo off |
| | | echo. |
| | | echo [��Ϣ] ���Web���̣�����dist�ļ��� |
| | | echo. |
| | | |
| | | %~d0 |
| | | cd %~dp0 |
| | | |
| | | cd .. |
| | | yarn build:prod |
| | | |
| | | pause |
| | |
| | | <meta name="renderer" content="webkit">
|
| | | <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
| | | <link rel="icon" href="/favicon.ico">
|
| | | <title>MISç³»ç»ï¼ç®¡çä¿¡æ¯ç³»ç»ï¼</title>
|
| | | <title>è¯å¯¼äºï¼ç®¡çä¿¡æ¯ç³»ç»ï¼</title>
|
| | | <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
|
| | | <style>
|
| | | html,
|
| | |
| | | { |
| | | "name": "ruoyi", |
| | | "version": "3.8.9", |
| | | "description": "MES", |
| | | "description": "MISç³»ç»ï¼ç®¡çä¿¡æ¯ç³»ç»ï¼", |
| | | "author": "è¥ä¾", |
| | | "license": "MIT", |
| | | "type": "module", |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | // æ¥è¯¢å
¬åå表 |
| | | export function listNotice(query) { |
| | | return request({ |
| | | url: '/collaborativeApproval/notice/list', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // æ¥è¯¢å
¬åè¯¦ç» |
| | | export function getNotice(noticeId) { |
| | | return request({ |
| | | url: '/collaborativeApproval/notice/' + noticeId, |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // æ°å¢å
Œ |
| | | export function addNotice(data) { |
| | | return request({ |
| | | url: '/collaborativeApproval/notice', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | // ä¿®æ¹å
Œ |
| | | export function updateNotice(data) { |
| | | return request({ |
| | | url: '/collaborativeApproval/notice', |
| | | method: 'put', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | // å é¤å
Œ |
| | | export function delNotice(noticeId) { |
| | | return request({ |
| | | url: '/collaborativeApproval/notice/' + noticeId, |
| | | method: 'delete' |
| | | }) |
| | | } |
| | | |
| | | // æ¹éå é¤å
Œ |
| | | export function delNoticeBatch(noticeIds) { |
| | | return request({ |
| | | url: '/collaborativeApproval/notice/batch', |
| | | method: 'delete', |
| | | data: noticeIds |
| | | }) |
| | | } |
| | | |
| | | // åå¸å
Œ |
| | | export function publishNotice(noticeId) { |
| | | return request({ |
| | | url: '/collaborativeApproval/notice/publish/' + noticeId, |
| | | method: 'put' |
| | | }) |
| | | } |
| | | |
| | | // ä¸çº¿å
Œ |
| | | export function offlineNotice(noticeId) { |
| | | return request({ |
| | | url: '/collaborativeApproval/notice/offline/' + noticeId, |
| | | method: 'put' |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢RPAå表 |
| | | export function listRpa(query) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢RPAè¯¦ç» |
| | | export function getRpa(rpaId) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa/" + rpaId, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢RPA |
| | | export function addRpa(data) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ä¿®æ¹RPA |
| | | export function updateRpa(data) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤RPA |
| | | export function delRpa(rpaId) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa/" + rpaId, |
| | | method: "delete", |
| | | }); |
| | | } |
| | | |
| | | // æ¹éå é¤RPA |
| | | export function delRpaBatch(rpaIds) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa/batch", |
| | | method: "delete", |
| | | data: rpaIds, |
| | | }); |
| | | } |
| | | |
| | | // å¯å¨RPA |
| | | export function startRpa(rpaId) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa/start/" + rpaId, |
| | | method: "post", |
| | | }); |
| | | } |
| | | |
| | | // 忢RPA |
| | | export function stopRpa(rpaId) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa/stop/" + rpaId, |
| | | method: "post", |
| | | }); |
| | | } |
| | | |
| | | // è·åRPAç¶æ |
| | | export function getRpaStatus(rpaId) { |
| | | return request({ |
| | | url: "/collaborativeApproval/rpa/status/" + rpaId, |
| | | method: "get", |
| | | }); |
| | | } |
| | |
| | | // 设å¤è½è-å页æ¥è¯¢ |
| | | export function equipmentEnergyListPage(query) { |
| | | return request({ |
| | | url: '/equipmentEnergyConsumption/listPage', |
| | | method: 'get', |
| | | url: "/equipmentEnergyConsumption/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }) |
| | | }); |
| | | } |
| | | // -è½æºè¶å¿-å页æ¥è¯¢ |
| | | export function listPageByTrend(query) { |
| | | return request({ |
| | | url: '/equipmentEnergyConsumption/listPageByTrend', |
| | | method: 'get', |
| | | url: "/equipmentEnergyConsumption/listPageByTrend", |
| | | method: "get", |
| | | params: query, |
| | | }) |
| | | }); |
| | | } |
| | | // åºå-å页æ¥è¯¢ |
| | | export function areaListPage(query) { |
| | | return request({ |
| | | url: "/electricityConsumptionArea/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // åºå-æ |
| | | export function areaListTree(query) { |
| | | return request({ |
| | | url: "/electricityConsumptionArea/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // æ¶é´å¨æ-å页æ¥è¯¢ |
| | | export function periodListPage(query) { |
| | | return request({ |
| | | url: "/energyPeriod/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // 设å¤è½è-å é¤ |
| | | export function equipmentEnergyDelete(query) { |
| | | return request({ |
| | | url: '/equipmentEnergyConsumption/delete', |
| | | method: 'delete', |
| | | url: "/equipmentEnergyConsumption/delete", |
| | | method: "delete", |
| | | data: query, |
| | | }) |
| | | }); |
| | | } |
| | | // åºå-å é¤ |
| | | export function areaDelete(query) { |
| | | return request({ |
| | | url: "/electricityConsumptionArea/delete", |
| | | method: "delete", |
| | | data: query, |
| | | }); |
| | | } |
| | | // æ¶é´å¨æ-å é¤ |
| | | export function periodDelete(query) { |
| | | return request({ |
| | | url: "/energyPeriod/delete", |
| | | method: "delete", |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | // 设å¤è½è-æ°å¢ |
| | | export function equipmentEnergyAdd(query) { |
| | | return request({ |
| | | url: '/equipmentEnergyConsumption/add', |
| | | method: 'post', |
| | | url: "/equipmentEnergyConsumption/add", |
| | | method: "post", |
| | | data: query, |
| | | }) |
| | | }); |
| | | } |
| | | // åºå-æ°å¢ |
| | | export function areaAdd(query) { |
| | | return request({ |
| | | url: "/electricityConsumptionArea/add", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | // æ¶é´å¨æ-æ°å¢ |
| | | export function periodAdd(query) { |
| | | return request({ |
| | | url: "/energyPeriod/add", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // 设å¤è½è-ä¿®æ¹ |
| | | export function equipmentEnergyUpdate(query) { |
| | | return request({ |
| | | url: '/equipmentEnergyConsumption/update', |
| | | method: 'post', |
| | | url: "/equipmentEnergyConsumption/update", |
| | | method: "post", |
| | | data: query, |
| | | }) |
| | | }); |
| | | } |
| | | //åºå-ä¿®æ¹ |
| | | export function areaUpdate(query) { |
| | | return request({ |
| | | url: "/electricityConsumptionArea/update", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // æ¶é´å¨æ-ä¿®æ¹ |
| | | export function periodUpdate(query) { |
| | | return request({ |
| | | url: "/energyPeriod/update", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | // 设å¤ä¸ææ¡æ¥è¯¢ |
| | | export function deviceList(query) { |
| | | return request({ |
| | | url: '/equipmentEnergyConsumption/deviceList', |
| | | method: 'get', |
| | | }) |
| | | } |
| | | url: "/equipmentEnergyConsumption/deviceList", |
| | | method: "get", |
| | | }); |
| | | } |
| | |
| | | // ç¨æ°´è®¾å¤-å页æ¥è¯¢ |
| | | export function waterEquipmentListPage(query) { |
| | | return request({ |
| | | url: '/waterEquipmentConsumption/listPage', |
| | | url: '/waterRecord/listPage', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | |
| | | // ç¨æ°´è¶å¿-å页æ¥è¯¢ |
| | | export function listPageByWaterTrend(query) { |
| | | return request({ |
| | | url: '/waterEquipmentConsumption/listPageByTrend', |
| | | url: '/waterRecord/listPageByTrend', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | |
| | | // ç¨æ°´è®¾å¤-å é¤ |
| | | export function waterEquipmentDelete(query) { |
| | | return request({ |
| | | url: '/waterEquipmentConsumption/delete', |
| | | url: '/waterRecord/delete', |
| | | method: 'delete', |
| | | data: query, |
| | | }) |
| | |
| | | // ç¨æ°´è®¾å¤-æ°å¢ |
| | | export function waterEquipmentAdd(query) { |
| | | return request({ |
| | | url: '/waterEquipmentConsumption/add', |
| | | url: '/waterRecord/add', |
| | | method: 'post', |
| | | data: query, |
| | | }) |
| | |
| | | // ç¨æ°´è®¾å¤-ä¿®æ¹ |
| | | export function waterEquipmentUpdate(query) { |
| | | return request({ |
| | | url: '/waterEquipmentConsumption/update', |
| | | url: '/waterRecord/update', |
| | | method: 'post', |
| | | data: query, |
| | | }) |
| | |
| | | // ç¨æ°´è®¾å¤ä¸ææ¡æ¥è¯¢ |
| | | export function waterDeviceList(query) { |
| | | return request({ |
| | | url: '/waterEquipmentConsumption/deviceList', |
| | | url: '/device/ledger/page', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | |
| | |
| | | data: query, |
| | | }) |
| | | } |
| | | |
| | |
| | | :expand-row-keys="expandRowKeys" |
| | | :show-summary="isShowSummary" |
| | | :summary-method="summaryMethod" |
| | | stripe |
| | | @row-click="rowClick" |
| | | @current-change="currentChange" |
| | | @selection-change="handleSelectionChange" |
| | |
| | | app.config.globalProperties.addDateRange = addDateRange;
|
| | | app.config.globalProperties.selectDictLabel = selectDictLabel;
|
| | | app.config.globalProperties.selectDictLabels = selectDictLabels;
|
| | | app.config.globalProperties.javaApi = "http://114.132.189.42:8099";
|
| | | app.config.globalProperties.javaApi = "http://114.132.189.42:7004";
|
| | | app.config.globalProperties.HaveJson = (val) => {
|
| | | return JSON.parse(JSON.stringify(val));
|
| | | };
|
| | |
| | | ]
|
| | | },
|
| | | {
|
| | | 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,
|
| | |
| | | ]
|
| | | },
|
| | | {
|
| | | path: '/main/MobileChat',
|
| | | component: Layout,
|
| | | redirect: '',
|
| | | hidden: true,
|
| | | permissions: ['MobileChat:edit'],
|
| | | children: [
|
| | | {
|
| | | path: '',
|
| | | component: () => import('@/views/chatHome/chatHomeIndex/MobileChat'),
|
| | | name: 'MobileChat',
|
| | | meta: { title: 'AI对è¯', activeMenu: '/chatHome/chatHomeIndex'}
|
| | | }
|
| | | ]
|
| | | },
|
| | | {
|
| | | path: '/system/role-auth',
|
| | | component: Layout,
|
| | | hidden: true,
|
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30" v-for="(contact, index) in formYYs.contactList" :key="index"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è系人ï¼" prop="contactPerson"> |
| | | <el-input v-model="contact.contactPerson" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èç³»çµè¯ï¼" prop="contactPhone"> |
| | | <div style="display: flex; align-items: center;width: 100%;"> |
| | | <el-input v-model="contact.contactPhone" placeholder="请è¾å
¥" clearable /> |
| | | <el-button @click="removeContact(index)" type="danger" circle style="margin-left: 5px;"> |
| | | <el-icon><Close /></el-icon> |
| | | </el-button> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-button @click="addNewContact" style="margin-bottom: 10px;">+ æ°å¢è系人</el-button> |
| | | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¶è¡åºæ¬æ·ï¼" prop="basicBankAccount"> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30" v-for="(contact, index) in formYYs.contactList" :key="index"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è系人ï¼" prop="contactPerson"> |
| | | <el-input v-model="contact.contactPerson" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èç³»çµè¯ï¼" prop="contactPhone"> |
| | | <div style="display: flex; align-items: center;width: 100%;"> |
| | | <el-input v-model="contact.contactPhone" placeholder="请è¾å
¥" clearable /> |
| | | <el-button @click="removeContact(index)" type="danger" circle style="margin-left: 5px;"> |
| | | <el-icon><Close /></el-icon> |
| | | </el-button> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-button @click="addNewContact" style="margin-bottom: 10px;">+ æ°å¢è系人</el-button> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»´æ¤äººï¼" prop="maintainer"> |
| | |
| | | } |
| | | chatList.value.push(replyMsg) |
| | | scrollBottom() |
| | | |
| | | loading.value = false |
| | | // å¦æææ¥è¯¢å
³é®åï¼åæ¨¡ææµå¼è¾åº |
| | | if (route.query.keyWord) { |
| | | simulateStreamingOutput(replyMsg, route.query.keyWord) |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éä»¶ææï¼" prop="remark"> |
| | | <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload |
| | | :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError" |
| | | :on-success="handleUploadSuccess" :on-remove="handleRemove"> |
| | | <el-button type="primary" v-if="operationType !== 'view'">ä¸ä¼ </el-button> |
| | | <template #tip v-if="operationType !== 'view'"> |
| | | <div class="el-upload__tip"> |
| | | æä»¶æ ¼å¼æ¯æ |
| | | docï¼docxï¼xlsï¼xlsxï¼pptï¼pptxï¼pdfï¼txtï¼xmlï¼jpgï¼jpegï¼pngï¼gifï¼bmpï¼rarï¼zipï¼7z |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | |
| | | approveProcessUpdate, |
| | | getDept |
| | | } from "@/api/collaborativeApproval/approvalProcess.js"; |
| | | import { |
| | | delLedgerFile, |
| | | } from "@/api/salesManagement/salesLedger.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | import { getToken } from "@/utils/auth"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | import useUserStore from "@/store/modules/user"; |
| | |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const fileList = ref([]); |
| | | const upload = reactive({ |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/file/upload", |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | }); |
| | | const data = reactive({ |
| | | form: { |
| | | approveTime: "", |
| | |
| | | approveDeptId: "", |
| | | approveReason: "", |
| | | checkResult: "", |
| | | tempFileIds: [], |
| | | approverList: [] // æ°å¢å段ï¼å卿æèç¹ç审æ¹äººid |
| | | }, |
| | | rules: { |
| | |
| | | const { form, rules } = toRefs(data); |
| | | const productOptions = ref([]); |
| | | const currentApproveStatus = ref(0) |
| | | const props = defineProps({ |
| | | approveType: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | } |
| | | }) |
| | | |
| | | // 审æ¹äººèç¹ç¸å
³ |
| | | const approverNodes = ref([ |
| | |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | console.log('openDialog', type, row) |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | userListNoPageByTenantId().then((res) => { |
| | |
| | | // è·åå½åç¨æ·ä¿¡æ¯å¹¶è®¾ç½®é¨é¨ID |
| | | form.value.approveDeptId = userStore.currentDeptId |
| | | if (operationType.value === 'edit') { |
| | | fileList.value = row.commonFileList |
| | | form.value.tempFileIds = fileList.value.map(file => file.id) |
| | | currentApproveStatus.value = row.approveStatus |
| | | approveProcessGetInfo({id: row.approveId,approveReason: '1'}).then(res => { |
| | | form.value = {...res.data} |
| | |
| | | const submitForm = () => { |
| | | // æ¶éææèç¹ç审æ¹äººid |
| | | form.value.approveUserIds = approverNodes.value.map(node => node.userId).join(',') |
| | | form.value.approveType = props.approveType |
| | | // 审æ¹äººå¿
å¡«æ ¡éª |
| | | const hasEmptyApprover = approverNodes.value.some(node => !node.userId) |
| | | if (hasEmptyApprover) { |
| | |
| | | } |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | fileList.value = [] |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | |
| | | const day = String(today.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | |
| | | // ä¸ä¼ åæ ¡æ£ |
| | | function handleBeforeUpload(file) { |
| | | // æ ¡æ£æä»¶å¤§å° |
| | | // if (file.size > 1024 * 1024 * 10) { |
| | | // proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶
è¿10MB!"); |
| | | // return false; |
| | | // } |
| | | proxy.$modal.loading("æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å..."); |
| | | return true; |
| | | } |
| | | // ä¸ä¼ 失败 |
| | | function handleUploadError(err) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤±è´¥"); |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | // ä¸ä¼ æååè° |
| | | function handleUploadSuccess(res, file, uploadFiles) { |
| | | proxy.$modal.closeLoading(); |
| | | if (res.code === 200) { |
| | | // ç¡®ä¿ tempFileIds åå¨ä¸ä¸ºæ°ç» |
| | | if (!form.value.tempFileIds) { |
| | | form.value.tempFileIds = []; |
| | | } |
| | | form.value.tempFileIds.push(res.data.tempId); |
| | | proxy.$modal.msgSuccess("ä¸ä¼ æå"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg); |
| | | proxy.$refs.fileUpload.handleRemove(file); |
| | | } |
| | | } |
| | | // ç§»é¤æä»¶ |
| | | function handleRemove(file) { |
| | | if (operationType.value === "edit") { |
| | | let ids = []; |
| | | ids.push(file.id); |
| | | delLedgerFile(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog v-model="dialogVisible" title="éä»¶" width="40%" :before-close="handleClose"> |
| | | <el-table :data="tableData" border height="40vh" stripe> |
| | | <el-table-column label="éä»¶åç§°" prop="name" min-width="400" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="æä½" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">ä¸è½½</el-button> |
| | | <el-button link type="primary" size="small" @click="lookFile(scope.row)">é¢è§</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-dialog> |
| | | <filePreview ref="filePreviewRef" /> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from 'vue' |
| | | import filePreview from '@/components/filePreview/index.vue' |
| | | |
| | | const dialogVisible = ref(false) |
| | | const tableData = ref([]) |
| | | const { proxy } = getCurrentInstance(); |
| | | const filePreviewRef = ref() |
| | | const handleClose = () => { |
| | | dialogVisible.value = false |
| | | } |
| | | const open = (list) => { |
| | | dialogVisible.value = true |
| | | tableData.value = list |
| | | } |
| | | const downLoadFile = (row) => { |
| | | proxy.$download.name(row.url); |
| | | |
| | | } |
| | | const lookFile = (row) => { |
| | | filePreviewRef.value.open(row.url) |
| | | } |
| | | defineExpose({ |
| | | open |
| | | }) |
| | | </script> |
| | | |
| | | <style></style> |
| | |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | </div> |
| | | <info-form-dia ref="infoFormDia" @close="handleQuery"></info-form-dia> |
| | | <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="approveType"></info-form-dia> |
| | | <approval-dia ref="approvalDia" @close="handleQuery"></approval-dia> |
| | | <FileList ref="fileListRef" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import FileList from "./fileList.vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref} from "vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | |
| | | import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue"; |
| | | import {approveProcessDelete, approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | |
| | | // å®ä¹ç»ä»¶æ¥æ¶çprops |
| | | const props = defineProps({ |
| | | approveType: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | } |
| | | }); |
| | | |
| | | const userStore = useUserStore(); |
| | | |
| | | |
| | |
| | | { |
| | | label: "ç³è¯·äºº", |
| | | prop: "approveUserName", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ç³è¯·æ¥æ", |
| | | prop: "approveTime", |
| | | width: 120 |
| | | width: 200 |
| | | }, |
| | | { |
| | | label: "ç»ææ¥æ", |
| | |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 150, |
| | | width: 230, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | |
| | | openApprovalDia('view', row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | downLoadFile(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const fileListRef = ref(null) |
| | | const downLoadFile = (row) => { |
| | | fileListRef.value.open(row.commonFileList) |
| | | |
| | | } |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | approveProcessListPage({...page, ...searchForm.value,}).then(res => { |
| | | approveProcessListPage({...page, ...searchForm.value,approveType:props.approveType}).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="container"> |
| | | <!-- å¼å
¥index.vueç»ä»¶å¹¶ä¼ éåæ° --> |
| | | <ApprovalProcessIndex :approveType="1" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import ApprovalProcessIndex from './index.vue' |
| | | |
| | | // å®ä¹ç»ä»¶åç§° |
| | | defineOptions({ |
| | | name: 'ApprovalProcessIndex1' |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .container { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="container"> |
| | | <!-- å¼å
¥index.vueç»ä»¶å¹¶ä¼ éåæ° --> |
| | | <ApprovalProcessIndex :approveType="2" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import ApprovalProcessIndex from './index.vue' |
| | | |
| | | // å®ä¹ç»ä»¶åç§° |
| | | defineOptions({ |
| | | name: 'ApprovalProcessIndex1' |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .container { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="container"> |
| | | <!-- å¼å
¥index.vueç»ä»¶å¹¶ä¼ éåæ° --> |
| | | <ApprovalProcessIndex :approveType="3" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import ApprovalProcessIndex from './index.vue' |
| | | |
| | | // å®ä¹ç»ä»¶åç§° |
| | | defineOptions({ |
| | | name: 'ApprovalProcessIndex1' |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .container { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="container"> |
| | | <!-- å¼å
¥index.vueç»ä»¶å¹¶ä¼ éåæ° --> |
| | | <ApprovalProcessIndex :approveType="4" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import ApprovalProcessIndex from './index.vue' |
| | | |
| | | // å®ä¹ç»ä»¶åç§° |
| | | defineOptions({ |
| | | name: 'ApprovalProcessIndex1' |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .container { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-tabs v-model="activeTab" type="border-card"> |
| | | <!-- åæè®¾ç½® --> |
| | | <el-tab-pane label="åæè®¾ç½®" name="holiday"> |
| | | <div class="tab-content"> |
| | | <el-button type="primary" @click="openDialog('holiday', 'add')">æ°å¢åæ</el-button> |
| | | |
| | | <el-table :data="holidayData" border style="width: 100%; margin-top: 20px;" stripe> |
| | | <el-table-column prop="name" label="åæåç§°" /> |
| | | <el-table-column prop="type" label="åæç±»å"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getTagType(scope.row.type)">{{ getTypeLabel(scope.row.type) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="startDate" label="å¼å§æ¥æ" /> |
| | | <el-table-column prop="endDate" label="ç»ææ¥æ" /> |
| | | <el-table-column prop="days" label="天æ°" align="center" /> |
| | | <el-table-column prop="status" 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-column label="æä½" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" size="small" @click="openDialog('holiday', 'edit', scope.row)">ç¼è¾</el-button> |
| | | <el-button type="danger" size="small" @click="deleteItem('holiday', scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- å¹´å设置 --> |
| | | <el-tab-pane label="å¹´å设置" name="annual"> |
| | | <div class="tab-content"> |
| | | <el-button type="primary" @click="openDialog('annual', 'add')">æ°å¢å¹´åè§å</el-button> |
| | | |
| | | <el-table :data="annualData" border style="width: 100%; margin-top: 20px;" stripe> |
| | | <el-table-column prop="employeeType" label="å工类å"/> |
| | | <el-table-column prop="workYears" label="å·¥ä½å¹´é" /> |
| | | <el-table-column prop="annualDays" label="å¹´å天æ°" align="center" /> |
| | | <el-table-column prop="maxCarryOver" label="æå¤§ç»è½¬å¤©æ°" align="center" /> |
| | | <el-table-column prop="status" 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-column label="æä½" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" size="small" @click="openDialog('annual', 'edit', scope.row)">ç¼è¾</el-button> |
| | | <el-button type="danger" size="small" @click="deleteItem('annual', scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- å ç设置 --> |
| | | <el-tab-pane label="å ç设置" name="overtime"> |
| | | <div class="tab-content"> |
| | | <el-button type="primary" @click="openDialog('overtime', 'add')">æ°å¢å çè§å</el-button> |
| | | |
| | | <el-table :data="overtimeData" border style="width: 100%; margin-top: 20px;" stripe> |
| | | <el-table-column prop="name" label="è§ååç§°" /> |
| | | <el-table-column prop="type" label="å çç±»å" > |
| | | <template #default="scope"> |
| | | <el-tag :type="getTagType(scope.row.type)">{{ getTypeLabel(scope.row.type) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="startTime" label="å¼å§æ¶é´" /> |
| | | <el-table-column prop="endTime" label="ç»ææ¶é´" /> |
| | | <el-table-column prop="rate" label="åç" align="center" /> |
| | | <el-table-column prop="status" 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-column label="æä½" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" size="small" @click="openDialog('overtime', 'edit', scope.row)">ç¼è¾</el-button> |
| | | <el-button type="danger" size="small" @click="deleteItem('overtime', scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- ä¸çæ¶é´è®¾ç½® --> |
| | | <el-tab-pane label="ä¸çæ¶é´è®¾ç½®" name="worktime"> |
| | | <div class="tab-content"> |
| | | <el-button type="primary" @click="openDialog('worktime', 'add')">æ°å¢æ¶é´æ®µ</el-button> |
| | | |
| | | <el-table :data="worktimeData" border style="width: 100%; margin-top: 20px;" stripe> |
| | | <el-table-column prop="name" label="æ¶é´æ®µåç§°" /> |
| | | <el-table-column prop="startTime" label="ä¸çæ¶é´"/> |
| | | <el-table-column prop="endTime" label="ä¸çæ¶é´" /> |
| | | <el-table-column prop="flexibleStart" label="å¼¹æ§ä¸ç"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.flexibleStart ? 'success' : 'info'"> |
| | | {{ scope.row.flexibleStart ? 'æ¯' : 'å¦' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="flexibleMinutes" label="å¼¹æ§æ¶é´(åé)" width="120" align="center" /> |
| | | <el-table-column prop="status" 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-column label="æä½" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" size="small" @click="openDialog('worktime', 'edit', scope.row)">ç¼è¾</el-button> |
| | | <el-button type="danger" size="small" @click="deleteItem('worktime', scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <!-- éç¨å¼¹çª --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px"> |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> |
| | | <el-form-item label="åç§°" prop="name" v-if="currentType !== 'annual'"> |
| | | <el-input v-model="form.name" placeholder="请è¾å
¥åç§°" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç±»å" prop="type" v-if="currentType === 'holiday' || currentType === 'overtime'"> |
| | | <el-select v-model="form.type" placeholder="è¯·éæ©ç±»å" style="width: 100%"> |
| | | <el-option |
| | | v-for="option in getTypeOptions()" |
| | | :key="option.value" |
| | | :label="option.label" |
| | | :value="option.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="å工类å" prop="employeeType" v-if="currentType === 'annual'"> |
| | | <el-select v-model="form.employeeType" placeholder="è¯·éæ©å工类å" style="width: 100%"> |
| | | <el-option label="æ£å¼åå·¥" value="regular" /> |
| | | <el-option label="è¯ç¨æåå·¥" value="probation" /> |
| | | <el-option label="å®ä¹ ç" value="intern" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="å·¥ä½å¹´é" prop="workYears" v-if="currentType === 'annual'"> |
| | | <el-input v-model="form.workYears" placeholder="å¦ï¼1-3å¹´ã3-5å¹´ç" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="å¹´å天æ°" prop="annualDays" v-if="currentType === 'annual'"> |
| | | <el-input-number v-model="form.annualDays" :min="0" :max="365" style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="æå¤§ç»è½¬å¤©æ°" prop="maxCarryOver" v-if="currentType === 'annual'"> |
| | | <el-input-number v-model="form.maxCarryOver" :min="0" :max="30" style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="æ¥æèå´" prop="dateRange" v-if="currentType === 'holiday'"> |
| | | <el-date-picker |
| | | v-model="form.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | style="width: 100%" |
| | | @change="calculateDays" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="天æ°" prop="days" v-if="currentType === 'holiday'"> |
| | | <el-input-number v-model="form.days" :min="0" style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="å¼å§æ¶é´" prop="startTime" v-if="currentType === 'overtime'"> |
| | | <el-time-picker |
| | | v-model="form.startTime" |
| | | placeholder="å¼å§æ¶é´" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('startTime')" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç»ææ¶é´" prop="endTime" v-if="currentType === 'overtime'"> |
| | | <el-time-picker |
| | | v-model="form.endTime" |
| | | placeholder="ç»ææ¶é´" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('endTime')" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="åç" prop="rate" v-if="currentType === 'overtime'"> |
| | | <el-input-number v-model="form.rate" :min="1" :max="3" :step="0.5" style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ä¸çæ¶é´" prop="workStartTime" v-if="currentType === 'worktime'"> |
| | | <el-time-picker |
| | | v-model="form.workStartTime" |
| | | placeholder="ä¸çæ¶é´" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('workStartTime')" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ä¸çæ¶é´" prop="workEndTime" v-if="currentType === 'worktime'"> |
| | | <el-time-picker |
| | | v-model="form.workEndTime" |
| | | placeholder="ä¸çæ¶é´" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | style="width: 100%" |
| | | @change="validateTimeField('workEndTime')" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="å¼¹æ§ä¸ç" prop="flexibleStart" v-if="currentType === 'worktime'"> |
| | | <el-switch v-model="form.flexibleStart" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="å¼¹æ§æ¶é´(åé)" prop="flexibleMinutes" v-if="currentType === 'worktime' && form.flexibleStart"> |
| | | <el-input-number v-model="form.flexibleMinutes" :min="0" :max="120" style="width: 100%" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-radio-group v-model="form.status"> |
| | | <el-radio value="active">å¯ç¨</el-radio> |
| | | <el-radio value="inactive">åç¨</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onUnmounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | // å½åæ¿æ´»çæ ç¾é¡µ |
| | | const activeTab = ref('holiday') |
| | | |
| | | // å¼¹çªç¸å
³ |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('') |
| | | const currentType = ref('') |
| | | const currentAction = ref('') |
| | | const currentEditId = ref('') |
| | | const formRef = ref() |
| | | |
| | | // è¡¨åæ°æ® |
| | | const form = reactive({ |
| | | name: '', |
| | | type: '', |
| | | dateRange: [], |
| | | days: 0, |
| | | employeeType: '', |
| | | workYears: '', |
| | | annualDays: 0, |
| | | maxCarryOver: 0, |
| | | startTime: '', // å çå¼å§æ¶é´ |
| | | endTime: '', // å çç»ææ¶é´ |
| | | workStartTime: '', // ä¸çæ¶é´ |
| | | workEndTime: '', // ä¸çæ¶é´ |
| | | rate: 1.5, |
| | | flexibleStart: false, |
| | | flexibleMinutes: 30, |
| | | status: 'active' |
| | | }) |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = { |
| | | name: [{ required: true, message: '请è¾å
¥åç§°', trigger: 'blur' }], |
| | | type: [{ required: true, message: 'è¯·éæ©ç±»å', trigger: 'change' }], |
| | | dateRange: [{ required: true, message: 'è¯·éæ©æ¥æèå´', trigger: 'change' }], |
| | | days: [{ required: true, message: '请è¾å
¥å¤©æ°', trigger: 'blur' }], |
| | | employeeType: [{ required: true, message: 'è¯·éæ©å工类å', trigger: 'change' }], |
| | | workYears: [{ required: true, message: '请è¾å
¥å·¥ä½å¹´é', trigger: 'blur' }], |
| | | annualDays: [{ required: true, message: '请è¾å
¥å¹´å天æ°', trigger: 'blur' }], |
| | | maxCarryOver: [{ required: true, message: '请è¾å
¥æå¤§ç»è½¬å¤©æ°', trigger: 'blur' }], |
| | | startTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©å¼å§æ¶é´', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error('è¯·éæ©å¼å§æ¶é´')) |
| | | } else { |
| | | callback() |
| | | } |
| | | } |
| | | }], |
| | | endTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©ç»ææ¶é´', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error('è¯·éæ©ç»ææ¶é´')) |
| | | } else { |
| | | callback() |
| | | } |
| | | } |
| | | }], |
| | | workStartTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©ä¸çæ¶é´', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error('è¯·éæ©ä¸çæ¶é´')) |
| | | } else { |
| | | callback() |
| | | } |
| | | } |
| | | }], |
| | | workEndTime: [{ |
| | | required: true, |
| | | message: 'è¯·éæ©ä¸çæ¶é´', |
| | | trigger: 'change', |
| | | validator: (rule, value, callback) => { |
| | | if (!value) { |
| | | callback(new Error('è¯·éæ©ä¸çæ¶é´')) |
| | | } else { |
| | | callback() |
| | | } |
| | | } |
| | | }], |
| | | rate: [{ required: true, message: '请è¾å
¥åç', trigger: 'blur' }] |
| | | } |
| | | |
| | | // æ¨¡ææ°æ® |
| | | const holidayData = ref([ |
| | | { id: '1', name: 'æ¥è', type: 'legal', startDate: '2024-02-10', endDate: '2024-02-17', days: 8, status: 'active' }, |
| | | { id: '2', name: 'æ¸
æè', type: 'legal', startDate: '2024-04-05', endDate: '2024-04-05', days: 1, status: 'active' }, |
| | | { id: '3', name: 'å³å¨è', type: 'legal', startDate: '2024-05-01', endDate: '2024-05-05', days: 5, status: 'active' } |
| | | ]) |
| | | |
| | | const annualData = ref([ |
| | | { id: '1', employeeType: 'regular', workYears: '1-3å¹´', annualDays: 5, maxCarryOver: 2, status: 'active' }, |
| | | { id: '2', employeeType: 'regular', workYears: '3-5å¹´', annualDays: 10, maxCarryOver: 5, status: 'active' }, |
| | | { id: '3', employeeType: 'regular', workYears: '5年以ä¸', annualDays: 15, maxCarryOver: 10, status: 'active' } |
| | | ]) |
| | | |
| | | const overtimeData = ref([ |
| | | { id: '1', name: '工使¥å ç', type: 'weekday', startTime: '18:00', endTime: '22:00', rate: 1.5, status: 'active' }, |
| | | { id: '2', name: '卿«å ç', type: 'weekend', startTime: '09:00', endTime: '18:00', rate: 2.0, status: 'active' }, |
| | | { id: '3', name: 'æ·±å¤å ç', type: 'night', startTime: '22:00', endTime: '06:00', rate: 2.5, status: 'active' } |
| | | ]) |
| | | |
| | | const worktimeData = ref([ |
| | | { id: '1', name: 'æ å工使¶é´', startTime: '09:00', endTime: '18:00', flexibleStart: true, flexibleMinutes: 30, status: 'active' }, |
| | | { id: '2', name: 'æ©çæ¶é´', startTime: '08:00', endTime: '17:00', flexibleStart: false, flexibleMinutes: 0, status: 'active' }, |
| | | { id: '3', name: 'æçæ¶é´', startTime: '14:00', endTime: '23:00', flexibleStart: false, flexibleMinutes: 0, status: 'active' } |
| | | ]) |
| | | |
| | | // å·¥å
·å½æ° |
| | | const getTagType = (type) => { |
| | | const tagMap = { |
| | | legal: 'success', adjustment: 'warning', special: 'info', company: 'primary', |
| | | weekday: 'primary', weekend: 'warning', holiday: 'danger', night: 'info' |
| | | } |
| | | return tagMap[type] || 'info' |
| | | } |
| | | |
| | | const getTypeLabel = (type) => { |
| | | const labelMap = { |
| | | legal: 'æ³å®è忥', adjustment: 'è°ä¼æ¥', special: 'ç¹æ®åæ', company: 'å
¬å¸åæ', |
| | | weekday: '工使¥å ç', weekend: '卿«å ç', holiday: 'è忥å ç', night: 'æ·±å¤å ç' |
| | | } |
| | | return labelMap[type] || type |
| | | } |
| | | |
| | | const getTypeOptions = () => { |
| | | if (currentType.value === 'holiday') { |
| | | return [ |
| | | { label: 'æ³å®è忥', value: 'legal' }, |
| | | { label: 'è°ä¼æ¥', value: 'adjustment' }, |
| | | { label: 'ç¹æ®åæ', value: 'special' }, |
| | | { label: 'å
¬å¸åæ', value: 'company' } |
| | | ] |
| | | } else if (currentType.value === 'overtime') { |
| | | return [ |
| | | { label: '工使¥å ç', value: 'weekday' }, |
| | | { label: '卿«å ç', value: 'weekend' }, |
| | | { label: 'è忥å ç', value: 'holiday' }, |
| | | { label: 'æ·±å¤å ç', value: 'night' } |
| | | ] |
| | | } |
| | | return [] |
| | | } |
| | | |
| | | // 计ç®åæå¤©æ° |
| | | const calculateDays = () => { |
| | | try { |
| | | if (form.dateRange && form.dateRange.length === 2 && form.dateRange[0] && form.dateRange[1]) { |
| | | const start = new Date(form.dateRange[0]) |
| | | const end = new Date(form.dateRange[1]) |
| | | |
| | | if (isNaN(start.getTime()) || isNaN(end.getTime())) { |
| | | console.warn('æ æçæ¥ææ ¼å¼') |
| | | return |
| | | } |
| | | |
| | | const diffTime = Math.abs(end - start) |
| | | const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1 |
| | | form.days = diffDays |
| | | } |
| | | } catch (error) { |
| | | console.error('计ç®å¤©æ°å¤±è´¥:', error) |
| | | } |
| | | } |
| | | |
| | | // éªè¯æ¶é´æ ¼å¼ |
| | | const validateTime = (time) => { |
| | | if (!time) return '' |
| | | if (typeof time === 'string') return time |
| | | if (time instanceof Date) { |
| | | return time.toTimeString().slice(0, 5) |
| | | } |
| | | return '' |
| | | } |
| | | |
| | | // éªè¯æ¶é´å段 |
| | | const validateTimeField = (fieldName) => { |
| | | try { |
| | | const value = form[fieldName] |
| | | if (value && typeof value === 'object' && value.hour !== undefined) { |
| | | // å¦ææ¯æ¶é´å¯¹è±¡ï¼è½¬æ¢ä¸ºåç¬¦ä¸²æ ¼å¼ |
| | | const hours = value.hour.toString().padStart(2, '0') |
| | | const minutes = value.minute.toString().padStart(2, '0') |
| | | form[fieldName] = `${hours}:${minutes}` |
| | | } |
| | | } catch (error) { |
| | | console.error(`éªè¯æ¶é´å段 ${fieldName} 失败:`, error) |
| | | form[fieldName] = '' |
| | | } |
| | | } |
| | | |
| | | // æå¼å¼¹çª |
| | | const openDialog = (type, action, row = null) => { |
| | | try { |
| | | currentType.value = type |
| | | currentAction.value = action |
| | | |
| | | if (action === 'add') { |
| | | dialogTitle.value = `æ°å¢${getTypeName(type)}` |
| | | currentEditId.value = '' |
| | | resetForm() |
| | | } else if (action === 'edit' && row) { |
| | | dialogTitle.value = `ç¼è¾${getTypeName(type)}` |
| | | currentEditId.value = row.id |
| | | fillForm(row) |
| | | } |
| | | |
| | | dialogVisible.value = true |
| | | } catch (error) { |
| | | console.error('æå¼å¼¹çªå¤±è´¥:', error) |
| | | ElMessage.error('æå¼å¼¹çªå¤±è´¥ï¼è¯·éè¯') |
| | | } |
| | | } |
| | | |
| | | const getTypeName = (type) => { |
| | | const nameMap = { |
| | | holiday: 'åæ', |
| | | annual: 'å¹´åè§å', |
| | | overtime: 'å çè§å', |
| | | worktime: 'æ¶é´æ®µ' |
| | | } |
| | | return nameMap[type] || '' |
| | | } |
| | | |
| | | const resetForm = () => { |
| | | Object.assign(form, { |
| | | name: '', |
| | | type: '', |
| | | dateRange: [], |
| | | days: 0, |
| | | employeeType: '', |
| | | workYears: '', |
| | | annualDays: 0, |
| | | maxCarryOver: 0, |
| | | startTime: '', |
| | | endTime: '', |
| | | workStartTime: '', |
| | | workEndTime: '', |
| | | rate: 1.5, |
| | | flexibleStart: false, |
| | | flexibleMinutes: 30, |
| | | status: 'active' |
| | | }) |
| | | } |
| | | |
| | | const fillForm = (row) => { |
| | | if (currentType.value === 'holiday') { |
| | | Object.assign(form, { |
| | | name: row.name, |
| | | type: row.type, |
| | | dateRange: [new Date(row.startDate), new Date(row.endDate)], |
| | | days: row.days, |
| | | status: row.status |
| | | }) |
| | | } else if (currentType.value === 'annual') { |
| | | Object.assign(form, { |
| | | employeeType: row.employeeType, |
| | | workYears: row.workYears, |
| | | annualDays: row.annualDays, |
| | | maxCarryOver: row.maxCarryOver, |
| | | status: row.status |
| | | }) |
| | | } else if (currentType.value === 'overtime') { |
| | | Object.assign(form, { |
| | | name: row.name, |
| | | type: row.type, |
| | | startTime: row.startTime || '', |
| | | endTime: row.endTime || '', |
| | | rate: row.rate, |
| | | status: row.status |
| | | }) |
| | | } else if (currentType.value === 'worktime') { |
| | | Object.assign(form, { |
| | | name: row.name, |
| | | workStartTime: row.startTime || '', |
| | | workEndTime: row.endTime || '', |
| | | flexibleStart: row.flexibleStart, |
| | | flexibleMinutes: row.flexibleMinutes, |
| | | status: row.status |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = async () => { |
| | | try { |
| | | if (!formRef.value) { |
| | | ElMessage.error('表åå¼ç¨ä¸åå¨') |
| | | return |
| | | } |
| | | |
| | | await formRef.value.validate() |
| | | |
| | | if (currentAction.value === 'add') { |
| | | addItem() |
| | | } else if (currentAction.value === 'edit') { |
| | | editItem() |
| | | } |
| | | |
| | | dialogVisible.value = false |
| | | ElMessage.success('æä½æå') |
| | | } catch (error) { |
| | | console.error('表åéªè¯å¤±è´¥:', error) |
| | | ElMessage.error('表åéªè¯å¤±è´¥ï¼è¯·æ£æ¥è¾å
¥') |
| | | } |
| | | } |
| | | |
| | | const addItem = () => { |
| | | const newItem = { ...form, id: Date.now().toString() } |
| | | |
| | | if (currentType.value === 'holiday') { |
| | | newItem.startDate = form.dateRange[0].toISOString().split('T')[0] |
| | | newItem.endDate = form.dateRange[1].toISOString().split('T')[0] |
| | | holidayData.value.push(newItem) |
| | | } else if (currentType.value === 'annual') { |
| | | annualData.value.push(newItem) |
| | | } else if (currentType.value === 'overtime') { |
| | | newItem.startTime = form.startTime || '' |
| | | newItem.endTime = form.endTime || '' |
| | | overtimeData.value.push(newItem) |
| | | } else if (currentType.value === 'worktime') { |
| | | newItem.startTime = form.workStartTime || '' |
| | | newItem.endTime = form.workEndTime || '' |
| | | worktimeData.value.push(newItem) |
| | | } |
| | | } |
| | | |
| | | const editItem = () => { |
| | | let dataArray |
| | | let index |
| | | |
| | | if (currentType.value === 'holiday') { |
| | | dataArray = holidayData.value |
| | | index = dataArray.findIndex(item => item.id === currentEditId.value) |
| | | if (index > -1) { |
| | | dataArray[index] = { |
| | | ...dataArray[index], |
| | | name: form.name, |
| | | type: form.type, |
| | | startDate: form.dateRange[0].toISOString().split('T')[0], |
| | | endDate: form.dateRange[1].toISOString().split('T')[0], |
| | | days: form.days, |
| | | status: form.status |
| | | } |
| | | } |
| | | } else if (currentType.value === 'annual') { |
| | | dataArray = annualData.value |
| | | index = dataArray.findIndex(item => item.id === currentEditId.value) |
| | | if (index > -1) { |
| | | dataArray[index] = { |
| | | ...dataArray[index], |
| | | employeeType: form.employeeType, |
| | | workYears: form.workYears, |
| | | annualDays: form.annualDays, |
| | | maxCarryOver: form.maxCarryOver, |
| | | status: form.status |
| | | } |
| | | } |
| | | } else if (currentType.value === 'overtime') { |
| | | dataArray = overtimeData.value |
| | | index = dataArray.findIndex(item => item.id === currentEditId.value) |
| | | if (index > -1) { |
| | | dataArray[index] = { |
| | | ...dataArray[index], |
| | | name: form.name, |
| | | type: form.type, |
| | | startTime: form.startTime || '', |
| | | endTime: form.endTime || '', |
| | | rate: form.rate, |
| | | status: form.status |
| | | } |
| | | } |
| | | } else if (currentType.value === 'worktime') { |
| | | dataArray = worktimeData.value |
| | | index = dataArray.findIndex(item => item.id === currentEditId.value) |
| | | if (index > -1) { |
| | | dataArray[index] = { |
| | | ...dataArray[index], |
| | | name: form.name, |
| | | startTime: form.workStartTime || '', |
| | | endTime: form.workEndTime || '', |
| | | flexibleStart: form.flexibleStart, |
| | | flexibleMinutes: form.flexibleMinutes, |
| | | status: form.status |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // å é¤é¡¹ç® |
| | | const deleteItem = (type, row) => { |
| | | ElMessageBox.confirm('ç¡®å®è¦å é¤è¿ä¸ªé¡¹ç®åï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | let dataArray |
| | | if (type === 'holiday') dataArray = holidayData.value |
| | | else if (type === 'annual') dataArray = annualData.value |
| | | else if (type === 'overtime') dataArray = overtimeData.value |
| | | else if (type === 'worktime') dataArray = worktimeData.value |
| | | |
| | | const index = dataArray.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | dataArray.splice(index, 1) |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | console.log('èå¤ç®¡ç页é¢å è½½å®æ') |
| | | }) |
| | | |
| | | onUnmounted(() => { |
| | | // æ¸
çå·¥ä½ |
| | | dialogVisible.value = false |
| | | currentType.value = '' |
| | | currentAction.value = '' |
| | | currentEditId.value = '' |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .tab-content { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | :deep(.el-tabs__content) { |
| | | padding: 20px; |
| | | } |
| | | |
| | | :deep(.el-form-item) { |
| | | margin-bottom: 20px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">ç¥è¯æ é¢ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.title" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥ç¥è¯æ é¢æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span class="search_title ml10">ç¥è¯ç±»åï¼</span> |
| | | <el-select v-model="searchForm.type" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="ååç¹æ¹" :value="'contract'" /> |
| | | <el-option label="å®¡æ¹æ¡ä¾" :value="'approval'" /> |
| | | <el-option label="è§£å³æ¹æ¡" :value="'solution'" /> |
| | | <el-option label="ç»éªæ»ç»" :value="'experience'" /> |
| | | <el-option label="æä½æå" :value="'guide'" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | æç´¢ |
| | | </el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢ç¥è¯</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾ç¥è¯å¼¹çª --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="800px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¥è¯æ é¢" prop="title"> |
| | | <el-input v-model="form.title" placeholder="请è¾å
¥ç¥è¯æ é¢" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¥è¯ç±»å" prop="type"> |
| | | <el-select v-model="form.type" placeholder="è¯·éæ©ç¥è¯ç±»å" style="width: 100%"> |
| | | <el-option label="ååç¹æ¹" value="contract" /> |
| | | <el-option label="å®¡æ¹æ¡ä¾" value="approval" /> |
| | | <el-option label="è§£å³æ¹æ¡" value="solution" /> |
| | | <el-option label="ç»éªæ»ç»" value="experience" /> |
| | | <el-option label="æä½æå" value="guide" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éç¨åºæ¯" prop="scenario"> |
| | | <el-input v-model="form.scenario" placeholder="请è¾å
¥éç¨åºæ¯" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§£å³æç" prop="efficiency"> |
| | | <el-select v-model="form.efficiency" placeholder="è¯·éæ©è§£å³æç" style="width: 100%"> |
| | | <el-option label="æ¾èæå" value="high" /> |
| | | <el-option label="ä¸è¬æå" value="medium" /> |
| | | <el-option label="轻微æå" value="low" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="é®é¢æè¿°" prop="problem"> |
| | | <el-input |
| | | v-model="form.problem" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请æè¿°éå°çé®é¢" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="è§£å³æ¹æ¡" prop="solution"> |
| | | <el-input |
| | | v-model="form.solution" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="è¯·è¯¦ç»æè¿°è§£å³æ¹æ¡" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="å
³é®è¦ç¹" prop="keyPoints"> |
| | | <el-input |
| | | v-model="form.keyPoints" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å
³é®è¦ç¹ï¼ç¨éå·åé" |
| | | /> |
| | | </el-form-item> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å建人" prop="creator"> |
| | | <el-input v-model="form.creator" placeholder="请è¾å
¥å建人" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä½¿ç¨æ¬¡æ°" prop="usageCount"> |
| | | <el-input-number v-model="form.usageCount" :min="0" style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ¥çç¥è¯è¯¦æ
å¼¹çª --> |
| | | <el-dialog |
| | | v-model="viewDialogVisible" |
| | | title="ç¥è¯è¯¦æ
" |
| | | width="900px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <div class="knowledge-detail"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="ç¥è¯æ é¢" :span="2"> |
| | | <span class="detail-title">{{ currentKnowledge.title }}</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç¥è¯ç±»å"> |
| | | <el-tag :type="getTypeTagType(currentKnowledge.type)"> |
| | | {{ getTypeLabel(currentKnowledge.type) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="éç¨åºæ¯"> |
| | | {{ currentKnowledge.scenario }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="è§£å³æç"> |
| | | <el-tag :type="getEfficiencyTagType(currentKnowledge.efficiency)"> |
| | | {{ getEfficiencyLabel(currentKnowledge.efficiency) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä½¿ç¨æ¬¡æ°"> |
| | | <el-tag type="info">{{ currentKnowledge.usageCount }} 次</el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å建人"> |
| | | {{ currentKnowledge.creator }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å建æ¶é´"> |
| | | {{ currentKnowledge.createTime }} |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="detail-section"> |
| | | <h4>é®é¢æè¿°</h4> |
| | | <div class="detail-content">{{ currentKnowledge.problem }}</div> |
| | | </div> |
| | | |
| | | <div class="detail-section"> |
| | | <h4>è§£å³æ¹æ¡</h4> |
| | | <div class="detail-content">{{ currentKnowledge.solution }}</div> |
| | | </div> |
| | | |
| | | <div class="detail-section"> |
| | | <h4>å
³é®è¦ç¹</h4> |
| | | <div class="key-points"> |
| | | <el-tag |
| | | v-for="(point, index) in currentKnowledge.keyPoints.split(',')" |
| | | :key="index" |
| | | type="success" |
| | | style="margin-right: 8px; margin-bottom: 8px;" |
| | | > |
| | | {{ point.trim() }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="detail-section"> |
| | | <h4>使ç¨ç»è®¡</h4> |
| | | <div class="usage-stats"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <div class="stat-item"> |
| | | <div class="stat-number">{{ currentKnowledge.usageCount }}</div> |
| | | <div class="stat-label">ä½¿ç¨æ¬¡æ°</div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <div class="stat-item"> |
| | | <div class="stat-number">{{ getEfficiencyScore(currentKnowledge.efficiency) }}%</div> |
| | | <div class="stat-label">æçæå</div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <div class="stat-item"> |
| | | <div class="stat-number">{{ getTimeSaved(currentKnowledge.efficiency) }}</div> |
| | | <div class="stat-label">å¹³åèçæ¶é´</div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="viewDialogVisible = false">å
³é</el-button> |
| | | <el-button type="primary" @click="copyKnowledge">å¤å¶ç¥è¯</el-button> |
| | | <el-button type="success" @click="markAsFavorite">æ¶è</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = { |
| | | title: [ |
| | | { required: true, message: "请è¾å
¥ç¥è¯æ é¢", trigger: "blur" } |
| | | ], |
| | | type: [ |
| | | { required: true, message: "è¯·éæ©ç¥è¯ç±»å", trigger: "change" } |
| | | ], |
| | | problem: [ |
| | | { required: true, message: "请æè¿°éå°çé®é¢", trigger: "blur" } |
| | | ], |
| | | solution: [ |
| | | { required: true, message: "è¯·è¯¦ç»æè¿°è§£å³æ¹æ¡", trigger: "blur" } |
| | | ] |
| | | }; |
| | | |
| | | // ååºå¼æ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | title: "", |
| | | type: "", |
| | | }, |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | | selectedIds: [], |
| | | form: { |
| | | title: "", |
| | | type: "", |
| | | scenario: "", |
| | | efficiency: "medium", |
| | | problem: "", |
| | | solution: "", |
| | | keyPoints: "", |
| | | creator: "", |
| | | usageCount: 0 |
| | | }, |
| | | dialogVisible: false, |
| | | dialogTitle: "", |
| | | dialogType: "add", |
| | | viewDialogVisible: false, |
| | | currentKnowledge: {} |
| | | }); |
| | | |
| | | const { |
| | | searchForm, |
| | | tableLoading, |
| | | page, |
| | | tableData, |
| | | selectedIds, |
| | | form, |
| | | dialogVisible, |
| | | dialogTitle, |
| | | dialogType, |
| | | viewDialogVisible, |
| | | currentKnowledge |
| | | } = toRefs(data); |
| | | |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(); |
| | | |
| | | // è¡¨æ ¼åé
ç½® |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ç¥è¯æ é¢", |
| | | prop: "title", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "ç¥è¯ç±»å", |
| | | prop: "type", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const typeMap = { |
| | | contract: "ååç¹æ¹", |
| | | approval: "å®¡æ¹æ¡ä¾", |
| | | solution: "è§£å³æ¹æ¡", |
| | | experience: "ç»éªæ»ç»", |
| | | guide: "æä½æå" |
| | | }; |
| | | return typeMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | contract: "success", |
| | | approval: "warning", |
| | | solution: "primary", |
| | | experience: "info", |
| | | guide: "danger" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "éç¨åºæ¯", |
| | | prop: "scenario", |
| | | width: 150, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "è§£å³æç", |
| | | prop: "efficiency", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const efficiencyMap = { |
| | | high: "æ¾èæå", |
| | | medium: "ä¸è¬æå", |
| | | low: "轻微æå" |
| | | }; |
| | | return efficiencyMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | high: "success", |
| | | medium: "warning", |
| | | low: "info" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "ä½¿ç¨æ¬¡æ°", |
| | | prop: "usageCount", |
| | | width: 100, |
| | | align: "center" |
| | | }, |
| | | { |
| | | label: "å建人", |
| | | prop: "creator", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "å建æ¶é´", |
| | | prop: "createTime", |
| | | width: 180, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 200, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | } |
| | | }, |
| | | { |
| | | name: "æ¥ç", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | viewKnowledge(row); |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | ]); |
| | | |
| | | // æ¨¡ææ°æ® |
| | | let mockData = [ |
| | | { |
| | | id: "1", |
| | | title: "ç¹æ®ååå®¡æ¹æµç¨ä¼åæ¹æ¡", |
| | | type: "contract", |
| | | scenario: "大é¢ååå¿«é审æ¹", |
| | | efficiency: "high", |
| | | problem: "大é¢ååå®¡æ¹æµç¨å¤æï¼å®¡æ¹æ¶é´é¿ï¼å½±åä¸å¡è¿å±", |
| | | solution: "建ç«ç»¿è²ééï¼å¯¹ç¬¦åæ¡ä»¶çååéç¨ç®åå®¡æ¹æµç¨ï¼ç±é¨é¨è´è´£äººç´æ¥å®¡æ¹ï¼å¹³åå®¡æ¹æ¶é´ä»3天缩çè³1天", |
| | | keyPoints: "绿è²é鿡件,ç®åæµç¨,å®¡æ¹æé,æ¶é´æ§å¶", |
| | | creator: "å¼ ç»ç", |
| | | usageCount: 15, |
| | | createTime: "2024-01-15 10:30:00" |
| | | }, |
| | | { |
| | | id: "2", |
| | | title: "è·¨é¨é¨åä½å®¡æ¹ç»éªæ»ç»", |
| | | type: "experience", |
| | | scenario: "å¤é¨é¨åä½é¡¹ç®", |
| | | efficiency: "medium", |
| | | problem: "è·¨é¨é¨é¡¹ç®å®¡æ¹æ¶ï¼åé¨é¨æè§ä¸ç»ä¸ï¼å®¡æ¹è¿åº¦ç¼æ
¢", |
| | | solution: "建ç«é¡¹ç®åè°æºå¶ï¼æå®é¡¹ç®è´è´£äººï¼å®æå¬å¼åè°ä¼è®®ï¼ç»ä¸åæ¹æè§ååè¿è¡å®¡æ¹", |
| | | keyPoints: "项ç®åè°,宿ä¼è®®,ç»ä¸æè§,è´è´£äººå¶åº¦", |
| | | creator: "æä¸»ç®¡", |
| | | usageCount: 8, |
| | | createTime: "2024-01-14 15:20:00" |
| | | }, |
| | | { |
| | | id: "3", |
| | | title: "ç´§æ¥éè´å®¡æ¹æä½æå", |
| | | type: "guide", |
| | | scenario: "ç´§æ¥éè´éæ±", |
| | | efficiency: "high", |
| | | problem: "ç´§æ¥éè´æ¶å®¡æ¹æµç¨å¤æï¼æ æ³æ»¡è¶³ç´§æ¥éæ±", |
| | | solution: "å¶å®ç´§æ¥éè´å®¡æ¹æ åï¼æç¡®ç´§æ¥ç¨åº¦å级ï¼ä¸å级å«éç¨ä¸åå®¡æ¹æµç¨ï¼ç¡®ä¿ç´§æ¥éæ±å¾å°åæ¶å¤ç", |
| | | keyPoints: "ç´§æ¥å级,æ åå¶å®,æµç¨ç®å,åæ¶å¤ç", |
| | | creator: "çä¸å", |
| | | usageCount: 12, |
| | | createTime: "2024-01-13 09:15:00" |
| | | } |
| | | ]; |
| | | |
| | | // ç¥è¯æ 颿¨¡æ¿ |
| | | const titleTemplates = [ |
| | | "{type}å®¡æ¹æµç¨ä¼åæ¹æ¡", |
| | | "{scenario}å¤çç»éªæ»ç»", |
| | | "{type}ç¹æ®æ
åµå¤çæå", |
| | | "{scenario}å¿«éå®¡æ¹æ¹æ¡", |
| | | "{type}æ ååæä½æµç¨", |
| | | "{scenario}é®é¢è§£å³æ¹æ¡", |
| | | "{type}æä½³å®è·µæ»ç»", |
| | | "{scenario}æçæåæ¹æ¡" |
| | | ]; |
| | | |
| | | // ç¥è¯ç±»åé
ç½® |
| | | const knowledgeTypes = [ |
| | | { type: "contract", label: "ååç¹æ¹", efficiency: "high" }, |
| | | { type: "approval", label: "å®¡æ¹æ¡ä¾", efficiency: "medium" }, |
| | | { type: "solution", label: "è§£å³æ¹æ¡", efficiency: "high" }, |
| | | { type: "experience", label: "ç»éªæ»ç»", efficiency: "medium" }, |
| | | { type: "guide", label: "æä½æå", efficiency: "low" } |
| | | ]; |
| | | |
| | | // åºæ¯å表 |
| | | const scenarios = ["大é¢åå审æ¹", "è·¨é¨é¨åä½", "ç´§æ¥éè´", "ç¹æ®ç³è¯·", "æµç¨ä¼å", "é®é¢å¤ç", "æ åå建设", "æçæå"]; |
| | | |
| | | // èªå¨çææ°æ°æ® |
| | | const generateNewData = () => { |
| | | const newId = (mockData.length + 1).toString(); |
| | | const now = new Date(); |
| | | const randomType = knowledgeTypes[Math.floor(Math.random() * knowledgeTypes.length)]; |
| | | const randomScenario = scenarios[Math.floor(Math.random() * scenarios.length)]; |
| | | |
| | | // çæéæºæ é¢ |
| | | let title = titleTemplates[Math.floor(Math.random() * titleTemplates.length)]; |
| | | title = title |
| | | .replace('{type}', randomType.label) |
| | | .replace('{scenario}', randomScenario); |
| | | |
| | | const newKnowledge = { |
| | | id: newId, |
| | | title: title, |
| | | type: randomType.type, |
| | | scenario: randomScenario, |
| | | efficiency: randomType.efficiency, |
| | | problem: `å¨${randomScenario}è¿ç¨ä¸éå°çé®é¢æè¿°...`, |
| | | solution: `é对${randomScenario}çè§£å³æ¹æ¡åæä½æ¥éª¤...`, |
| | | keyPoints: "å
³é®è¦ç¹1,å
³é®è¦ç¹2,å
³é®è¦ç¹3,å
³é®è¦ç¹4", |
| | | creator: ["å¼ ç»ç", "æä¸»ç®¡", "çä¸å", "åæ»ç"][Math.floor(Math.random() * 4)], |
| | | usageCount: Math.floor(Math.random() * 20) + 1, |
| | | createTime: now.toLocaleString() |
| | | }; |
| | | |
| | | // æ·»å å°æ°æ®å¼å¤´ |
| | | mockData.unshift(newKnowledge); |
| | | |
| | | // ä¿ææ°æ®éå¨åçèå´å
ï¼æå¤ä¿ç30æ¡ï¼ |
| | | if (mockData.length > 30) { |
| | | mockData = mockData.slice(0, 30); |
| | | } |
| | | |
| | | console.log(`[${new Date().toLocaleString()}] èªå¨çææ°ç¥è¯: ${title}`); |
| | | }; |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | getList(); |
| | | startAutoRefresh(); |
| | | }); |
| | | |
| | | // å¼å§èªå¨å·æ° |
| | | const startAutoRefresh = () => { |
| | | setInterval(() => { |
| | | generateNewData(); |
| | | getList(); |
| | | }, 600000); // 10åéå·æ°ä¸æ¬¡ (10 * 60 * 1000 = 600000ms) |
| | | }; |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const handleQuery = () => { |
| | | page.value.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | |
| | | setTimeout(() => { |
| | | let filteredData = [...mockData]; |
| | | |
| | | if (searchForm.value.title) { |
| | | filteredData = filteredData.filter(item => |
| | | item.title.toLowerCase().includes(searchForm.value.title.toLowerCase()) |
| | | ); |
| | | } |
| | | |
| | | if (searchForm.value.type) { |
| | | filteredData = filteredData.filter(item => item.type === searchForm.value.type); |
| | | } |
| | | |
| | | tableData.value = filteredData; |
| | | page.value.total = filteredData.length; |
| | | tableLoading.value = false; |
| | | }, 500); |
| | | }; |
| | | |
| | | // å页å¤ç |
| | | const pagination = (obj) => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // éæ©ååå¤ç |
| | | const handleSelectionChange = (selection) => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | }; |
| | | |
| | | // æå¼è¡¨å |
| | | const openForm = (type, row = null) => { |
| | | dialogType.value = type; |
| | | if (type === "add") { |
| | | dialogTitle.value = "æ°å¢ç¥è¯"; |
| | | // é置表å |
| | | Object.assign(form.value, { |
| | | title: "", |
| | | type: "", |
| | | scenario: "", |
| | | efficiency: "medium", |
| | | problem: "", |
| | | solution: "", |
| | | keyPoints: "", |
| | | creator: "", |
| | | usageCount: 0 |
| | | }); |
| | | } else if (type === "edit" && row) { |
| | | dialogTitle.value = "ç¼è¾ç¥è¯"; |
| | | Object.assign(form.value, { |
| | | title: row.title, |
| | | type: row.type, |
| | | scenario: row.scenario, |
| | | efficiency: row.efficiency, |
| | | problem: row.problem, |
| | | solution: row.solution, |
| | | keyPoints: row.keyPoints, |
| | | creator: row.creator, |
| | | usageCount: row.usageCount |
| | | }); |
| | | } |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // æ¥çç¥è¯è¯¦æ
|
| | | const viewKnowledge = (row) => { |
| | | currentKnowledge.value = { ...row }; |
| | | viewDialogVisible.value = true; |
| | | }; |
| | | |
| | | // è·åç±»åæ ç¾ç±»å |
| | | const getTypeTagType = (type) => { |
| | | const typeMap = { |
| | | contract: "success", |
| | | approval: "warning", |
| | | solution: "primary", |
| | | experience: "info", |
| | | guide: "danger" |
| | | }; |
| | | return typeMap[type] || "info"; |
| | | }; |
| | | |
| | | // è·åç±»åæ ç¾ææ¬ |
| | | const getTypeLabel = (type) => { |
| | | const typeMap = { |
| | | contract: "ååç¹æ¹", |
| | | approval: "å®¡æ¹æ¡ä¾", |
| | | solution: "è§£å³æ¹æ¡", |
| | | experience: "ç»éªæ»ç»", |
| | | guide: "æä½æå" |
| | | }; |
| | | return typeMap[type] || type; |
| | | }; |
| | | |
| | | // è·åæçæ ç¾ç±»å |
| | | const getEfficiencyTagType = (efficiency) => { |
| | | const typeMap = { |
| | | high: "success", |
| | | medium: "warning", |
| | | low: "info" |
| | | }; |
| | | return typeMap[efficiency] || "info"; |
| | | }; |
| | | |
| | | // è·åæçæ ç¾ææ¬ |
| | | const getEfficiencyLabel = (efficiency) => { |
| | | const efficiencyMap = { |
| | | high: "æ¾èæå", |
| | | medium: "ä¸è¬æå", |
| | | low: "轻微æå" |
| | | }; |
| | | return efficiencyMap[efficiency] || efficiency; |
| | | }; |
| | | |
| | | // è·åæçæåç¾åæ¯ |
| | | const getEfficiencyScore = (efficiency) => { |
| | | const scoreMap = { |
| | | high: 40, |
| | | medium: 25, |
| | | low: 15 |
| | | }; |
| | | return scoreMap[efficiency] || 0; |
| | | }; |
| | | |
| | | // è·åå¹³åèçæ¶é´ |
| | | const getTimeSaved = (efficiency) => { |
| | | const timeMap = { |
| | | high: "2-3天", |
| | | medium: "1-2天", |
| | | low: "0.5-1天" |
| | | }; |
| | | return timeMap[efficiency] || "æªç¥"; |
| | | }; |
| | | |
| | | // å¤å¶ç¥è¯ |
| | | const copyKnowledge = () => { |
| | | const knowledgeText = ` |
| | | ç¥è¯æ é¢ï¼${currentKnowledge.value.title} |
| | | ç¥è¯ç±»åï¼${getTypeLabel(currentKnowledge.value.type)} |
| | | éç¨åºæ¯ï¼${currentKnowledge.value.scenario} |
| | | é®é¢æè¿°ï¼${currentKnowledge.value.problem} |
| | | è§£å³æ¹æ¡ï¼${currentKnowledge.value.solution} |
| | | å
³é®è¦ç¹ï¼${currentKnowledge.value.keyPoints} |
| | | å建人ï¼${currentKnowledge.value.creator} |
| | | `.trim(); |
| | | |
| | | // å¤å¶å°åªè´´æ¿ |
| | | navigator.clipboard.writeText(knowledgeText).then(() => { |
| | | ElMessage.success("ç¥è¯å
容已å¤å¶å°åªè´´æ¿"); |
| | | }).catch(() => { |
| | | ElMessage.error("å¤å¶å¤±è´¥ï¼è¯·æå¨å¤å¶"); |
| | | }); |
| | | }; |
| | | |
| | | // æ¶èç¥è¯ |
| | | const markAsFavorite = () => { |
| | | // å¢å ä½¿ç¨æ¬¡æ° |
| | | const index = mockData.findIndex(item => item.id === currentKnowledge.value.id); |
| | | if (index !== -1) { |
| | | mockData[index].usageCount += 1; |
| | | currentKnowledge.value.usageCount += 1; |
| | | } |
| | | |
| | | ElMessage.success("å·²æ¶èï¼ä½¿ç¨æ¬¡æ°+1"); |
| | | }; |
| | | |
| | | // æäº¤ç¥è¯è¡¨å |
| | | const submitForm = async () => { |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | if (dialogType.value === "add") { |
| | | // æ°å¢ç¥è¯ |
| | | const newKnowledge = { |
| | | id: (mockData.length + 1).toString(), |
| | | title: form.value.title, |
| | | type: form.value.type, |
| | | scenario: form.value.scenario, |
| | | efficiency: form.value.efficiency, |
| | | problem: form.value.problem, |
| | | solution: form.value.solution, |
| | | keyPoints: form.value.keyPoints, |
| | | creator: form.value.creator, |
| | | usageCount: form.value.usageCount, |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | |
| | | mockData.unshift(newKnowledge); |
| | | ElMessage.success("ç¥è¯å建æå"); |
| | | } else { |
| | | // ç¼è¾ç¥è¯ |
| | | const index = mockData.findIndex(item => item.id === selectedIds.value[0]); |
| | | if (index !== -1) { |
| | | Object.assign(mockData[index], { |
| | | title: form.value.title, |
| | | type: form.value.type, |
| | | scenario: form.value.scenario, |
| | | efficiency: form.value.efficiency, |
| | | problem: form.value.problem, |
| | | solution: form.value.solution, |
| | | keyPoints: form.value.keyPoints, |
| | | creator: form.value.creator, |
| | | usageCount: form.value.usageCount |
| | | }); |
| | | ElMessage.success("ç¥è¯æ´æ°æå"); |
| | | } |
| | | } |
| | | |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } catch (error) { |
| | | console.error("表åéªè¯å¤±è´¥:", error); |
| | | } |
| | | }; |
| | | |
| | | // å é¤ç¥è¯ |
| | | const handleDelete = () => { |
| | | if (selectedIds.value.length === 0) { |
| | | ElMessage.warning("è¯·éæ©è¦å é¤çç¥è¯"); |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | // ä»mockDataä¸å é¤éä¸ç项 |
| | | selectedIds.value.forEach(id => { |
| | | const index = mockData.findIndex(item => item.id === id); |
| | | if (index !== -1) { |
| | | mockData.splice(index, 1); |
| | | } |
| | | }); |
| | | |
| | | ElMessage.success("å 餿å"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .auto-refresh-info { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .auto-refresh-info .el-alert { |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | .knowledge-detail { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .detail-title { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | |
| | | .detail-section { |
| | | margin-top: 24px; |
| | | } |
| | | |
| | | .detail-section h4 { |
| | | margin: 0 0 12px 0; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | border-left: 4px solid #409eff; |
| | | padding-left: 12px; |
| | | } |
| | | |
| | | .detail-content { |
| | | background: #f8f9fa; |
| | | padding: 16px; |
| | | border-radius: 6px; |
| | | line-height: 1.6; |
| | | color: #606266; |
| | | white-space: pre-wrap; |
| | | } |
| | | |
| | | .key-points { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .usage-stats { |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .stat-item { |
| | | text-align: center; |
| | | padding: 20px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .stat-number { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .stat-label { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>ä¼è®®çæ¿</h2> |
| | | <!-- <el-button type="primary" @click="createMeeting">å建ä¼è®®</el-button>--> |
| | | </div> |
| | | |
| | | <!-- ä¼è®®ç»è®¡å¡ç --> |
| | | <div class="stats-cards"> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-number">{{ stats.total }}</div> |
| | | <div class="stat-label">æ»ä¼è®®æ°</div> |
| | | </div> |
| | | </el-card> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-number">{{ stats.ongoing }}</div> |
| | | <div class="stat-label">è¿è¡ä¸</div> |
| | | </div> |
| | | </el-card> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-number">{{ stats.completed }}</div> |
| | | <div class="stat-label">已宿</div> |
| | | </div> |
| | | </el-card> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-number">{{ stats.upcoming }}</div> |
| | | <div class="stat-label">å³å°å¼å§</div> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- ä¼è®®å表 --> |
| | | <div class="meeting-list"> |
| | | <el-card v-for="meeting in meetings" :key="meeting.id" class="meeting-card"> |
| | | <div class="meeting-header"> |
| | | <div class="meeting-title"> |
| | | <h3>{{ meeting.title }}</h3> |
| | | <el-tag :type="getStatusType(meeting.status)" size="small"> |
| | | {{ getStatusText(meeting.status) }} |
| | | </el-tag> |
| | | </div> |
| | | <div class="meeting-time"> |
| | | <el-icon><Clock /></el-icon> |
| | | {{ formatTime(meeting.startTime) }} - {{ formatTime(meeting.endTime) }} |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="meeting-info"> |
| | | <div class="info-item"> |
| | | <el-icon><Location /></el-icon> |
| | | <span>{{ meeting.location }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <el-icon><User /></el-icon> |
| | | <span>主æäºº: {{ meeting.host }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <el-icon><UserFilled /></el-icon> |
| | | <span>åä¼äººæ°: {{ meeting.participants.length }}人</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="meeting-agenda"> |
| | | <h4>è®®ç¨å®æ</h4> |
| | | <div class="agenda-list"> |
| | | <div |
| | | v-for="(agenda, index) in meeting.agenda" |
| | | :key="index" |
| | | class="agenda-item" |
| | | :class="{ 'active': agenda.status === 'active', 'completed': agenda.status === 'completed' }" |
| | | > |
| | | <span class="agenda-time">{{ agenda.time }}</span> |
| | | <span class="agenda-content">{{ agenda.content }}</span> |
| | | <el-tag |
| | | :type="getAgendaStatusType(agenda.status)" |
| | | size="small" |
| | | > |
| | | {{ getAgendaStatusText(agenda.status) }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- <div class="meeting-actions">--> |
| | | <!-- <el-button type="primary" size="small" @click="joinMeeting(meeting)">--> |
| | | <!-- å å
¥ä¼è®®--> |
| | | <!-- </el-button>--> |
| | | <!-- <el-button type="info" size="small" @click="viewDetails(meeting)">--> |
| | | <!-- æ¥ç详æ
--> |
| | | <!-- </el-button>--> |
| | | <!-- <el-button type="warning" size="small" @click="editMeeting(meeting)">--> |
| | | <!-- ç¼è¾--> |
| | | <!-- </el-button>--> |
| | | <!-- </div>--> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- å建ä¼è®®å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="dialogVisible" title="å建ä¼è®®" width="600px"> |
| | | <el-form :model="meetingForm" label-width="100px"> |
| | | <el-form-item label="ä¼è®®æ é¢"> |
| | | <el-input v-model="meetingForm.title" placeholder="请è¾å
¥ä¼è®®æ é¢" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¼è®®æ¶é´"> |
| | | <el-date-picker |
| | | v-model="meetingForm.timeRange" |
| | | type="datetimerange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¶é´" |
| | | end-placeholder="ç»ææ¶é´" |
| | | format="YYYY-MM-DD HH:mm" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¼è®®å°ç¹"> |
| | | <el-input v-model="meetingForm.location" placeholder="请è¾å
¥ä¼è®®å°ç¹" /> |
| | | </el-form-item> |
| | | <el-form-item label="主æäºº"> |
| | | <el-input v-model="meetingForm.host" placeholder="请è¾å
¥ä¸»æäººå§å" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¼è®®æè¿°"> |
| | | <el-input |
| | | v-model="meetingForm.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥ä¼è®®æè¿°" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitMeeting">ç¡®å®</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { Clock, Location, User, UserFilled } from '@element-plus/icons-vue' |
| | | |
| | | // ç»è®¡æ°æ® |
| | | const stats = reactive({ |
| | | total: 12, |
| | | ongoing: 3, |
| | | completed: 7, |
| | | upcoming: 2 |
| | | }) |
| | | |
| | | // ä¼è®®æ°æ® |
| | | const meetings = ref([ |
| | | { |
| | | id: 1, |
| | | title: '产åå¼åå¨ä¼', |
| | | status: 'ongoing', |
| | | startTime: '2024-01-15 09:00:00', |
| | | endTime: '2024-01-15 10:30:00', |
| | | location: 'ä¼è®®å®¤A', |
| | | host: 'å¼ ç»ç', |
| | | participants: ['å¼ ç»ç', 'æå·¥ç¨å¸', 'ç设计å¸', 'èµµæµè¯å'], |
| | | agenda: [ |
| | | { time: '09:00-09:15', content: 'ä¸å¨å·¥ä½æ»ç»', status: 'completed' }, |
| | | { time: '09:15-09:45', content: 'æ¬å¨å¼å计å', status: 'active' }, |
| | | { time: '09:45-10:00', content: 'ææ¯é¾ç¹è®¨è®º', status: 'pending' }, |
| | | { time: '10:00-10:30', content: 'é®é¢åé¦ä¸è§£å³', status: 'pending' } |
| | | ] |
| | | }, |
| | | { |
| | | id: 2, |
| | | title: '客æ·éæ±è¯å®¡ä¼', |
| | | status: 'upcoming', |
| | | startTime: '2024-01-15 14:00:00', |
| | | endTime: '2024-01-15 15:00:00', |
| | | location: '线ä¸ä¼è®®', |
| | | host: 'éæ»ç', |
| | | participants: ['éæ»ç', 'å产åç»ç', 'å客æ·ç»ç', '客æ·ä»£è¡¨'], |
| | | agenda: [ |
| | | { time: '14:00-14:20', content: 'éæ±èæ¯ä»ç»', status: 'pending' }, |
| | | { time: '14:20-14:40', content: 'åè½éæ±åæ', status: 'pending' }, |
| | | { time: '14:40-15:00', content: 'ææ¯å¯è¡æ§è¯ä¼°', status: 'pending' } |
| | | ] |
| | | }, |
| | | { |
| | | id: 3, |
| | | title: 'å¢é建设活å¨', |
| | | status: 'completed', |
| | | startTime: '2024-01-14 16:00:00', |
| | | endTime: '2024-01-14 18:00:00', |
| | | location: 'å
¬å¸å¤§å
', |
| | | host: '人äºé¨', |
| | | participants: ['å
¨ä½åå·¥'], |
| | | agenda: [ |
| | | { time: '16:00-16:30', content: 'å¢é游æ', status: 'completed' }, |
| | | { time: '16:30-17:00', content: 'ç»éªå享', status: 'completed' }, |
| | | { time: '17:00-18:00', content: 'èªç±äº¤æµ', status: 'completed' } |
| | | ] |
| | | } |
| | | ]) |
| | | |
| | | // å¯¹è¯æ¡ç¸å
³ |
| | | const dialogVisible = ref(false) |
| | | const meetingForm = reactive({ |
| | | title: '', |
| | | timeRange: [], |
| | | location: '', |
| | | host: '', |
| | | description: '' |
| | | }) |
| | | |
| | | // è·åç¶æç±»å |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | 'ongoing': 'success', |
| | | 'upcoming': 'warning', |
| | | 'completed': 'info' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | // è·åç¶æææ¬ |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | 'ongoing': 'è¿è¡ä¸', |
| | | 'upcoming': 'å³å°å¼å§', |
| | | 'completed': '已宿' |
| | | } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | // è·åè®®ç¨ç¶æç±»å |
| | | const getAgendaStatusType = (status) => { |
| | | const statusMap = { |
| | | 'completed': 'success', |
| | | 'active': 'warning', |
| | | 'pending': 'info' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | // è·åè®®ç¨ç¶æææ¬ |
| | | const getAgendaStatusText = (status) => { |
| | | const statusMap = { |
| | | 'completed': '已宿', |
| | | 'active': 'è¿è¡ä¸', |
| | | 'pending': 'å¾
å¼å§' |
| | | } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | // æ ¼å¼åæ¶é´ |
| | | const formatTime = (timeStr) => { |
| | | const date = new Date(timeStr) |
| | | return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) |
| | | } |
| | | |
| | | // å建ä¼è®® |
| | | const createMeeting = () => { |
| | | dialogVisible.value = true |
| | | // é置表å |
| | | Object.assign(meetingForm, { |
| | | title: '', |
| | | timeRange: [], |
| | | location: '', |
| | | host: '', |
| | | description: '' |
| | | }) |
| | | } |
| | | |
| | | // æäº¤ä¼è®® |
| | | const submitMeeting = () => { |
| | | if (!meetingForm.title || !meetingForm.timeRange.length || !meetingForm.location || !meetingForm.host) { |
| | | ElMessage.warning('请填å宿´çä¼è®®ä¿¡æ¯') |
| | | return |
| | | } |
| | | |
| | | // å建æ°ä¼è®® |
| | | const newMeeting = { |
| | | id: Date.now(), |
| | | title: meetingForm.title, |
| | | status: 'upcoming', |
| | | startTime: meetingForm.timeRange[0], |
| | | endTime: meetingForm.timeRange[1], |
| | | location: meetingForm.location, |
| | | host: meetingForm.host, |
| | | participants: [meetingForm.host], |
| | | agenda: [ |
| | | { time: 'å¾
å®', content: 'è®®ç¨å¾
å®', status: 'pending' } |
| | | ] |
| | | } |
| | | |
| | | meetings.value.unshift(newMeeting) |
| | | stats.total++ |
| | | stats.upcoming++ |
| | | |
| | | ElMessage.success('ä¼è®®å建æå') |
| | | dialogVisible.value = false |
| | | } |
| | | |
| | | // å å
¥ä¼è®® |
| | | const joinMeeting = (meeting) => { |
| | | ElMessage.success(`å·²å å
¥ä¼è®®ï¼${meeting.title}`) |
| | | } |
| | | |
| | | // æ¥ç详æ
|
| | | const viewDetails = (meeting) => { |
| | | ElMessage.info(`æ¥çä¼è®®è¯¦æ
ï¼${meeting.title}`) |
| | | } |
| | | |
| | | // ç¼è¾ä¼è®® |
| | | const editMeeting = (meeting) => { |
| | | ElMessage.info(`ç¼è¾ä¼è®®ï¼${meeting.title}`) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | console.log('ä¼è®®çæ¿é¡µé¢å è½½å®æ') |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .stats-cards { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
| | | gap: 20px; |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .stat-card { |
| | | text-align: center; |
| | | } |
| | | |
| | | .stat-content { |
| | | padding: 10px; |
| | | } |
| | | |
| | | .stat-number { |
| | | font-size: 32px; |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .stat-label { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .meeting-list { |
| | | display: grid; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .meeting-card { |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .meeting-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .meeting-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .meeting-title h3 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .meeting-time { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 5px; |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .meeting-info { |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .info-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 5px; |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .meeting-agenda { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .meeting-agenda h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .agenda-list { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .agenda-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 15px; |
| | | padding: 10px; |
| | | border-radius: 6px; |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | .agenda-item.active { |
| | | background-color: #fdf6ec; |
| | | border-left: 3px solid #e6a23c; |
| | | } |
| | | |
| | | .agenda-item.completed { |
| | | background-color: #f0f9ff; |
| | | border-left: 3px solid #409eff; |
| | | } |
| | | |
| | | .agenda-time { |
| | | font-weight: bold; |
| | | color: #606266; |
| | | min-width: 80px; |
| | | } |
| | | |
| | | .agenda-content { |
| | | flex: 1; |
| | | color: #303133; |
| | | } |
| | | |
| | | .meeting-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .stats-cards { |
| | | grid-template-columns: repeat(2, 1fr); |
| | | } |
| | | |
| | | .meeting-header { |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .meeting-info { |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .meeting-actions { |
| | | flex-direction: column; |
| | | } |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- æç´¢è¡¨å --> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">å
¬åæ é¢ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.noticeTitle" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å
¬åæ é¢æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span class="search_title ml10">å
¬åç±»åï¼</span> |
| | | <el-select v-model="searchForm.noticeType" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="æ¾åéç¥" value="1" /> |
| | | <el-option label="设å¤ç»´ä¿®éç¥" value="2" /> |
| | | </el-select> |
| | | <span class="search_title ml10">ç¶æï¼</span> |
| | | <el-select v-model="searchForm.status" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="è稿" value="0" /> |
| | | <el-option label="å·²åå¸" value="1" /> |
| | | <el-option label="å·²ä¸çº¿" value="2" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> |
| | | <el-button @click="resetQuery" style="margin-left: 10px">éç½®</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢å
Œ</el-button> |
| | | <el-button type="danger" plain @click="handleDelete" :disabled="!selectedIds.length">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- éç¥å
¬åæ¿ --> |
| | | <div class="notice-board"> |
| | | <!-- æ¾åéç¥åºå --> |
| | | <div class="notice-section" v-if="holidayNotices.length > 0"> |
| | | <div class="section-header"> |
| | | <h3>ð
æ¾åéç¥</h3> |
| | | <span class="section-count">{{ holidayNotices.length }}æ¡</span> |
| | | </div> |
| | | <div class="notice-cards"> |
| | | <div |
| | | v-for="notice in holidayNotices" |
| | | :key="notice.id" |
| | | class="notice-card holiday-card" |
| | | :class="{ 'urgent': notice.priority === '3' }" |
| | | > |
| | | <div class="card-header"> |
| | | <div class="card-title"> |
| | | <el-icon class="holiday-icon"><Calendar /></el-icon> |
| | | {{ notice.noticeTitle }} |
| | | </div> |
| | | <div class="card-actions"> |
| | | <el-button link type="primary" @click="handleEdit(notice)">ç¼è¾</el-button> |
| | | <el-button link type="danger" @click="handleDelete(notice.id)">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="card-content"> |
| | | <p>{{ notice.noticeContent }}</p> |
| | | </div> |
| | | <div class="card-footer"> |
| | | <div class="card-meta"> |
| | | <span class="priority" :class="'priority-' + notice.priority"> |
| | | {{ getPriorityText(notice.priority) }} |
| | | </span> |
| | | <span class="status" :class="'status-' + notice.status"> |
| | | {{ getStatusText(notice.status) }} |
| | | </span> |
| | | </div> |
| | | <div class="card-info"> |
| | | <span class="creator">{{ notice.createBy }}</span> |
| | | <span class="time">{{ notice.createTime }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="card-remark" v-if="notice.remark"> |
| | | <el-icon><InfoFilled /></el-icon> |
| | | <span>{{ notice.remark }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 设å¤ç»´ä¿®éç¥åºå --> |
| | | <div class="notice-section" v-if="maintenanceNotices.length > 0"> |
| | | <div class="section-header"> |
| | | <h3>ð§ 设å¤ç»´ä¿®éç¥</h3> |
| | | <span class="section-count">{{ maintenanceNotices.length }}æ¡</span> |
| | | </div> |
| | | <div class="notice-cards"> |
| | | <div |
| | | v-for="notice in maintenanceNotices" |
| | | :key="notice.id" |
| | | class="notice-card maintenance-card" |
| | | :class="{ 'urgent': notice.priority === '3' }" |
| | | > |
| | | <div class="card-header"> |
| | | <div class="card-title"> |
| | | <el-icon class="maintenance-icon"><Tools /></el-icon> |
| | | {{ notice.noticeTitle }} |
| | | </div> |
| | | <div class="card-actions"> |
| | | <el-button link type="primary" @click="handleEdit(notice)">ç¼è¾</el-button> |
| | | <el-button link type="danger" @click="handleDelete(notice.id)">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="card-content"> |
| | | <p>{{ notice.noticeContent }}</p> |
| | | </div> |
| | | <div class="card-footer"> |
| | | <div class="card-meta"> |
| | | <span class="priority" :class="'priority-' + notice.priority"> |
| | | {{ getPriorityText(notice.priority) }} |
| | | </span> |
| | | <span class="status" :class="'status-' + notice.status"> |
| | | {{ getStatusText(notice.status) }} |
| | | </span> |
| | | </div> |
| | | <div class="card-info"> |
| | | <span class="creator">{{ notice.createBy }}</span> |
| | | <span class="time">{{ notice.createTime }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="card-remark" v-if="notice.remark"> |
| | | <el-icon><InfoFilled /></el-icon> |
| | | <span>{{ notice.remark }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç©ºç¶æ --> |
| | | <div class="empty-state" v-if="filteredNotices.length === 0"> |
| | | <el-empty description="ææ éç¥å
Œ" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | :title="dialogTitle" |
| | | v-model="dialogVisible" |
| | | width="800px" |
| | | append-to-body |
| | | @close="resetForm" |
| | | > |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¬åæ é¢" prop="noticeTitle"> |
| | | <el-input v-model="form.noticeTitle" placeholder="请è¾å
¥å
¬åæ é¢" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¬åç±»å" prop="noticeType"> |
| | | <el-select v-model="form.noticeType" placeholder="è¯·éæ©å
¬åç±»å" style="width: 100%"> |
| | | <el-option label="æ¾åéç¥" value="1" /> |
| | | <el-option label="设å¤ç»´ä¿®éç¥" value="2" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æ"> |
| | | <el-radio-group v-model="form.status"> |
| | | <el-radio value="0">è稿</el-radio> |
| | | <el-radio value="1">å·²åå¸</el-radio> |
| | | <el-radio value="2">å·²ä¸çº¿</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼å
级"> |
| | | <el-select v-model="form.priority" placeholder="è¯·éæ©ä¼å
级" style="width: 100%"> |
| | | <el-option label="æ®é" value="1" /> |
| | | <el-option label="éè¦" value="2" /> |
| | | <el-option label="ç´§æ¥" value="3" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="å
¬åå
容" prop="noticeContent"> |
| | | <el-input |
| | | v-model="form.noticeContent" |
| | | type="textarea" |
| | | :rows="6" |
| | | placeholder="请è¾å
¥å
¬åå
容" |
| | | maxlength="500" |
| | | show-word-limit |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input |
| | | v-model="form.remark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" |
| | | maxlength="200" |
| | | show-word-limit |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">ç¡® å®</el-button> |
| | | <el-button @click="dialogVisible = false">å æ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search, Calendar, Tools, InfoFilled } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs, computed } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | |
| | | const userStore = useUserStore(); |
| | | |
| | | // ååºå¼æ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | noticeTitle: "", |
| | | noticeType: "", |
| | | status: "", |
| | | }, |
| | | form: { |
| | | id: undefined, |
| | | noticeTitle: "", |
| | | noticeType: "", |
| | | noticeContent: "", |
| | | status: "0", |
| | | priority: "1", |
| | | remark: "", |
| | | createBy: "", |
| | | createTime: "", |
| | | }, |
| | | rules: { |
| | | noticeTitle: [ |
| | | { required: true, message: "å
¬åæ é¢ä¸è½ä¸ºç©º", trigger: "blur" } |
| | | ], |
| | | noticeType: [ |
| | | { required: true, message: "è¯·éæ©å
¬åç±»å", trigger: "change" } |
| | | ], |
| | | noticeContent: [ |
| | | { required: true, message: "å
¬åå
容ä¸è½ä¸ºç©º", trigger: "blur" } |
| | | ] |
| | | } |
| | | }); |
| | | |
| | | const { searchForm, form, rules } = toRefs(data); |
| | | |
| | | // 页é¢ç¶æ |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const selectedIds = ref([]); |
| | | const formRef = ref(); |
| | | |
| | | // æ¨¡ææ°æ® - æ ¹æ®æ³å®èåæ¥è®¾è®¡ |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | noticeTitle: "2024å¹´æ¥èæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "2", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024å¹´æ¥èæ¾å宿å¦ä¸ï¼2æ10æ¥ï¼åä¸ï¼è³2æ17æ¥ï¼åå
«ï¼æ¾åè°ä¼ï¼å
±8天ã2æ4æ¥ï¼æææ¥ï¼ã2æ18æ¥ï¼æææ¥ï¼ä¸çã请åé¨é¨æåå好工ä½å®æã", |
| | | remark: "æ¾åæé´è¯·ä¿æææºç
éï¼å¦æç´§æ¥äºå¡åæ¶èç³»", |
| | | createBy: "人äºé¨", |
| | | createTime: "2024-01-15 10:30:00" |
| | | }, |
| | | { |
| | | id: 2, |
| | | noticeTitle: "2024å¹´æ¸
æèæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "1", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024å¹´æ¸
æèæ¾å宿å¦ä¸ï¼4æ4æ¥ï¼ææåï¼è³4æ6æ¥ï¼ææå
ï¼æ¾åè°ä¼ï¼å
±3天ã4æ7æ¥ï¼æææ¥ï¼ä¸çã", |
| | | remark: "请åé¨é¨å好å¼ç宿ï¼ç¡®ä¿èæ¥æé´å项工使£å¸¸è¿è½¬", |
| | | createBy: "è¡æ¿é¨", |
| | | createTime: "2024-01-14 14:20:00" |
| | | }, |
| | | { |
| | | id: 3, |
| | | noticeTitle: "2024å¹´å³å¨èæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "1", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024å¹´å³å¨èæ¾å宿å¦ä¸ï¼5æ1æ¥ï¼ææä¸ï¼è³5æ5æ¥ï¼æææ¥ï¼æ¾åè°ä¼ï¼å
±5天ã4æ28æ¥ï¼æææ¥ï¼ã5æ11æ¥ï¼ææå
ï¼ä¸çã", |
| | | remark: "æ¾åå请å
³éçµæºï¼é好é¨çªï¼æ³¨æå®å
¨", |
| | | createBy: "è¡æ¿é¨", |
| | | createTime: "2024-01-13 09:15:00" |
| | | }, |
| | | { |
| | | id: 4, |
| | | noticeTitle: "2024年端åèæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "1", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024年端åèæ¾å宿å¦ä¸ï¼6æ8æ¥ï¼ææå
ï¼è³6æ10æ¥ï¼ææä¸ï¼æ¾åè°ä¼ï¼å
±3天ã6æ11æ¥ï¼ææäºï¼ä¸çã", |
| | | remark: "ç¥å¤§å®¶ç«¯åèå¿«ä¹ï¼é家幸ç¦ï¼", |
| | | createBy: "è¡æ¿é¨", |
| | | createTime: "2024-01-12 16:30:00" |
| | | }, |
| | | { |
| | | id: 5, |
| | | noticeTitle: "2024å¹´ä¸ç§èæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "1", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024å¹´ä¸ç§èæ¾å宿å¦ä¸ï¼9æ15æ¥ï¼æææ¥ï¼è³9æ17æ¥ï¼ææäºï¼æ¾åè°ä¼ï¼å
±3天ã9æ14æ¥ï¼ææå
ï¼ä¸çã", |
| | | remark: "ä¸ç§ä½³èï¼ç¥å¤§å®¶å¢åç¾æ»¡ï¼å¹¸ç¦å®åº·ï¼", |
| | | createBy: "è¡æ¿é¨", |
| | | createTime: "2024-01-11 11:20:00" |
| | | }, |
| | | { |
| | | id: 6, |
| | | noticeTitle: "2024å¹´å½åºèæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "2", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024å¹´å½åºèæ¾å宿å¦ä¸ï¼10æ1æ¥ï¼ææäºï¼è³10æ7æ¥ï¼ææä¸ï¼æ¾åè°ä¼ï¼å
±7天ã9æ29æ¥ï¼æææ¥ï¼ã10æ12æ¥ï¼ææå
ï¼ä¸çã", |
| | | remark: "å½åºæé´è¯·åé¨é¨å好å¼ç宿ï¼ç¡®ä¿å®å
¨ç¨³å®", |
| | | createBy: "è¡æ¿é¨", |
| | | createTime: "2024-01-10 15:45:00" |
| | | }, |
| | | { |
| | | id: 7, |
| | | noticeTitle: "A车é´ç产线年度æ£ä¿®éç¥", |
| | | noticeType: "2", |
| | | priority: "2", |
| | | status: "1", |
| | | noticeContent: "A车é´ç产线å°äº2024å¹´1æ20æ¥ï¼å¨å
ï¼è¿è¡å¹´åº¦æ£ä¿®ç»´æ¤ï¼é¢è®¡åå·¥8å°æ¶ãæ£ä¿®å
容å
æ¬ï¼è®¾å¤æ¸
æ´ã润æ»ä¿å
»ãå®å
¨è£
ç½®æ£æ¥çã请ç产é¨é¨æåè°æ´ç产计åã", |
| | | remark: "ç»´ä¿®æé´è¯·ç¸å
³äººåé
åï¼ç¡®ä¿æ£ä¿®å·¥ä½å®å
¨é¡ºå©è¿è¡", |
| | | createBy: "设å¤é¨", |
| | | createTime: "2024-01-14 14:20:00" |
| | | }, |
| | | { |
| | | id: 8, |
| | | noticeTitle: "B车é´è®¾å¤é¢é²æ§ç»´æ¤éç¥", |
| | | noticeType: "2", |
| | | priority: "1", |
| | | status: "1", |
| | | noticeContent: "B车é´å
³é®è®¾å¤å°äº2024å¹´1æ25æ¥è¿è¡é¢é²æ§ç»´æ¤ï¼é¢è®¡åå·¥4å°æ¶ãç»´æ¤å
容å
æ¬ï¼è®¾å¤æ£æ¥ãé¶ä»¶æ´æ¢ãæ§è½æµè¯çã请ç¸å
³é¨é¨é
åã", |
| | | remark: "ç»´æ¤å®æåå°è¿è¡è¯è¿è¡ï¼ç¡®ä¿è®¾å¤æ£å¸¸è¿è¡", |
| | | createBy: "设å¤é¨", |
| | | createTime: "2024-01-13 09:15:00" |
| | | } |
| | | ]; |
| | | |
| | | // 计ç®å±æ§ |
| | | const filteredNotices = computed(() => { |
| | | let filtered = [...mockData]; |
| | | |
| | | if (searchForm.value.noticeTitle) { |
| | | filtered = filtered.filter(item => |
| | | item.noticeTitle.includes(searchForm.value.noticeTitle) |
| | | ); |
| | | } |
| | | if (searchForm.value.noticeType) { |
| | | filtered = filtered.filter(item => |
| | | item.noticeType === searchForm.value.noticeType |
| | | ); |
| | | } |
| | | if (searchForm.value.status !== "") { |
| | | filtered = filtered.filter(item => |
| | | item.status === searchForm.value.status |
| | | ); |
| | | } |
| | | |
| | | return filtered; |
| | | }); |
| | | |
| | | const holidayNotices = computed(() => { |
| | | return filteredNotices.value.filter(notice => notice.noticeType === "1"); |
| | | }); |
| | | |
| | | const maintenanceNotices = computed(() => { |
| | | return filteredNotices.value.filter(notice => notice.noticeType === "2"); |
| | | }); |
| | | |
| | | // æ¹æ³å®ä¹ |
| | | const handleQuery = () => { |
| | | // æç´¢åè½ä¿æä¸åï¼ä½æ°æ®éè¿è®¡ç®å±æ§èªå¨è¿æ»¤ |
| | | }; |
| | | |
| | | const resetQuery = () => { |
| | | searchForm.value = { |
| | | noticeTitle: "", |
| | | noticeType: "", |
| | | status: "" |
| | | }; |
| | | }; |
| | | |
| | | const getPriorityText = (priority) => { |
| | | const priorityMap = { "1": "æ®é", "2": "éè¦", "3": "ç´§æ¥" }; |
| | | return priorityMap[priority] || "æ®é"; |
| | | }; |
| | | |
| | | const getStatusText = (status) => { |
| | | const statusMap = { "0": "è稿", "1": "å·²åå¸", "2": "å·²ä¸çº¿" }; |
| | | return statusMap[status] || "æªç¥"; |
| | | }; |
| | | |
| | | const openForm = (type) => { |
| | | if (type === 'add') { |
| | | dialogTitle.value = "æ°å¢å
Œ"; |
| | | form.value = { |
| | | id: undefined, |
| | | noticeTitle: "", |
| | | noticeType: "", |
| | | noticeContent: "", |
| | | status: "0", |
| | | priority: "1", |
| | | remark: "", |
| | | createBy: userStore.name || "å½åç¨æ·", |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | } |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const handleEdit = (row) => { |
| | | dialogTitle.value = "ç¼è¾å
Œ"; |
| | | form.value = { ...row }; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const handleSelectionChange = (selection) => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | }; |
| | | |
| | | const handleDelete = (id) => { |
| | | ElMessageBox.confirm( |
| | | "确认å é¤è¿æ¡å
¬ååï¼", |
| | | "æç¤º", |
| | | { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning" |
| | | } |
| | | ).then(() => { |
| | | const index = mockData.findIndex(item => item.id === id); |
| | | if (index > -1) { |
| | | mockData.splice(index, 1); |
| | | ElMessage.success("å 餿å"); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | if (form.value.id) { |
| | | // ç¼è¾æ¨¡å¼ |
| | | const index = mockData.findIndex(item => item.id === form.value.id); |
| | | if (index > -1) { |
| | | mockData[index] = { ...form.value }; |
| | | } |
| | | ElMessage.success("ä¿®æ¹æå"); |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | const newId = Math.max(...mockData.map(item => item.id)) + 1; |
| | | const newNotice = { |
| | | ...form.value, |
| | | id: newId, |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | mockData.unshift(newNotice); |
| | | ElMessage.success("æ°å¢æå"); |
| | | } |
| | | dialogVisible.value = false; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const resetForm = () => { |
| | | formRef.value?.resetFields(); |
| | | }; |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | // 页é¢å è½½å®æ |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search_form { |
| | | background: #fff; |
| | | padding: 20px; |
| | | margin-bottom: 20px; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .search_title { |
| | | font-weight: 500; |
| | | color: #333; |
| | | margin-right: 8px; |
| | | } |
| | | |
| | | .ml10 { |
| | | margin-left: 10px; |
| | | } |
| | | |
| | | .notice-board { |
| | | background: #f5f7fa; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .notice-section { |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .section-header { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | padding: 0 10px; |
| | | } |
| | | |
| | | .section-header h3 { |
| | | margin: 0; |
| | | color: #303133; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .section-count { |
| | | margin-left: 10px; |
| | | background: #409eff; |
| | | color: white; |
| | | padding: 2px 8px; |
| | | border-radius: 12px; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .notice-cards { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); |
| | | gap: 20px; |
| | | } |
| | | |
| | | .notice-card { |
| | | background: white; |
| | | border-radius: 12px; |
| | | padding: 20px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | transition: all 0.3s ease; |
| | | border-left: 4px solid transparent; |
| | | } |
| | | |
| | | .notice-card:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | .holiday-card { |
| | | border-left-color: #67c23a; |
| | | } |
| | | |
| | | .maintenance-card { |
| | | border-left-color: #e6a23c; |
| | | } |
| | | |
| | | .urgent { |
| | | border-left-color: #f56c6c; |
| | | background: linear-gradient(135deg, #fff5f5 0%, #ffffff 100%); |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .card-title { |
| | | display: flex; |
| | | align-items: center; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | flex: 1; |
| | | } |
| | | |
| | | .holiday-icon { |
| | | color: #67c23a; |
| | | margin-right: 8px; |
| | | font-size: 18px; |
| | | } |
| | | |
| | | .maintenance-icon { |
| | | color: #e6a23c; |
| | | margin-right: 8px; |
| | | font-size: 18px; |
| | | } |
| | | |
| | | .card-actions { |
| | | display: flex; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .card-content { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .card-content p { |
| | | margin: 0; |
| | | color: #606266; |
| | | line-height: 1.6; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .card-footer { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .card-meta { |
| | | display: flex; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .priority, .status { |
| | | padding: 2px 8px; |
| | | border-radius: 12px; |
| | | font-size: 12px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .priority-1 { background: #f0f9ff; color: #0369a1; } |
| | | .priority-2 { background: #fef3c7; color: #d97706; } |
| | | .priority-3 { background: #fef2f2; color: #dc2626; } |
| | | |
| | | .status-0 { background: #f3f4f6; color: #6b7280; } |
| | | .status-1 { background: #d1fae5; color: #059669; } |
| | | .status-2 { background: #fef3c7; color: #d97706; } |
| | | |
| | | .card-info { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: flex-end; |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .creator { |
| | | font-weight: 500; |
| | | margin-bottom: 2px; |
| | | } |
| | | |
| | | .card-remark { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | padding: 8px 12px; |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | font-size: 12px; |
| | | color: #606266; |
| | | border-left: 3px solid #409eff; |
| | | } |
| | | |
| | | .empty-state { |
| | | text-align: center; |
| | | padding: 60px 20px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | /* ååºå¼è®¾è®¡ */ |
| | | @media (max-width: 768px) { |
| | | .notice-cards { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | |
| | | .search_form { |
| | | flex-direction: column; |
| | | gap: 15px; |
| | | } |
| | | |
| | | .search_form > div { |
| | | width: 100%; |
| | | } |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">éç¥æ é¢ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.title" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥éç¥æ é¢æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span class="search_title ml10">éç¥ç±»åï¼</span> |
| | | <el-select v-model="searchForm.type" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="æ¾åéç¥" :value="'holiday'" /> |
| | | <el-option label="å¤ç½éç¥" :value="'penalty'" /> |
| | | <el-option label="å¼ä¼éç¥" :value="'meeting'" /> |
| | | <el-option label="临æ¶éç¥" :value="'temporary'" /> |
| | | <el-option label="æ£å¼éç¥" :value="'formal'" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | æç´¢ |
| | | </el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢éç¥</el-button> |
| | | <el-button type="success" @click="openMeetingDialog">å¨çº¿ä¼è®®</el-button> |
| | | <el-button type="warning" @click="openFileShareDialog">æä»¶å
񄧮</el-button> |
| | | <!-- <el-button type="info" @click="refreshEmployees">å·æ°åå·¥</el-button> --> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾éç¥å¼¹çª --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="800px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éç¥æ é¢" prop="title"> |
| | | <el-input v-model="form.title" placeholder="请è¾å
¥éç¥æ é¢" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éç¥ç±»å" prop="type"> |
| | | <el-select v-model="form.type" placeholder="è¯·éæ©éç¥ç±»å" style="width: 100%"> |
| | | <el-option label="æ¾åéç¥" value="holiday" /> |
| | | <el-option label="å¤ç½éç¥" value="penalty" /> |
| | | <el-option label="å¼ä¼éç¥" value="meeting" /> |
| | | <el-option label="临æ¶éç¥" value="temporary" /> |
| | | <el-option label="æ£å¼éç¥" value="formal" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼å
级" prop="priority"> |
| | | <el-select v-model="form.priority" placeholder="è¯·éæ©ä¼å
级" style="width: 100%"> |
| | | <el-option label="æ®é" value="low" /> |
| | | <el-option label="éè¦" value="medium" /> |
| | | <el-option label="ç´§æ¥" value="high" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æææè³" prop="expireDate"> |
| | | <el-date-picker |
| | | v-model="form.expireDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©æææ" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="æ¥æ¶é¨é¨" prop="departments"> |
| | | <el-select |
| | | v-model="form.departments" |
| | | multiple |
| | | placeholder="è¯·éæ©æ¥æ¶é¨é¨" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="dept in departments" |
| | | :key="dept" |
| | | :label="dept" |
| | | :value="dept" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="忥æ¹å¼" prop="syncMethods"> |
| | | <el-checkbox-group v-model="form.syncMethods"> |
| | | <el-checkbox |
| | | v-for="method in syncMethods" |
| | | :key="method.value" |
| | | :label="method.value" |
| | | > |
| | | {{ method.label }} |
| | | </el-checkbox> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | <el-form-item label="éç¥å
容" prop="content"> |
| | | <el-input |
| | | v-model="form.content" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请è¾å
¥éç¥å
容" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- å¨çº¿ä¼è®®å¼¹çª --> |
| | | <el-dialog |
| | | v-model="meetingDialogVisible" |
| | | title="å建å¨çº¿ä¼è®®" |
| | | width="700px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form ref="meetingFormRef" :model="meetingForm" :rules="meetingRules" label-width="120px"> |
| | | <el-form-item label="ä¼è®®æ é¢" prop="title"> |
| | | <el-input v-model="meetingForm.title" placeholder="请è¾å
¥ä¼è®®æ é¢" /> |
| | | </el-form-item> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¼å§æ¶é´" prop="startTime"> |
| | | <el-date-picker |
| | | v-model="meetingForm.startTime" |
| | | type="datetime" |
| | | placeholder="è¯·éæ©å¼å§æ¶é´" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼è®®æ¶é¿" prop="duration"> |
| | | <el-input-number |
| | | v-model="meetingForm.duration" |
| | | :min="15" |
| | | :max="480" |
| | | :step="15" |
| | | style="width: 100%" |
| | | /> |
| | | <span style="margin-left: 10px">åé</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="ä¼è®®å¹³å°" prop="platform"> |
| | | <el-select v-model="meetingForm.platform" placeholder="è¯·éæ©ä¼è®®å¹³å°" style="width: 100%"> |
| | | <el-option |
| | | v-for="platform in meetingPlatforms" |
| | | :key="platform.value" |
| | | :label="platform.label" |
| | | :value="platform.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="åä¼äººå" prop="participants"> |
| | | <el-select |
| | | v-model="meetingForm.participants" |
| | | multiple |
| | | filterable |
| | | remote |
| | | :remote-method="filterEmployees" |
| | | :loading="employeesLoading" |
| | | placeholder="è¯·éæ©åä¼äººå" |
| | | style="width: 100%" |
| | | > |
| | | <el-option-group |
| | | v-for="group in employeeGroups" |
| | | :key="group.label" |
| | | :label="group.label" |
| | | > |
| | | <el-option |
| | | v-for="employee in group.options" |
| | | :key="employee.value" |
| | | :label="`${employee.label} (${employee.dept})`" |
| | | :value="employee.value" |
| | | > |
| | | <div style="display: flex; justify-content: space-between; align-items: center;"> |
| | | <div> |
| | | <div style="font-weight: 500;">{{ employee.label }}</div> |
| | | <div style="color: #909399; font-size: 12px;">{{ employee.dept }}</div> |
| | | </div> |
| | | <div style="text-align: right; font-size: 12px; color: #909399;"> |
| | | <div v-if="employee.phone">{{ employee.phone }}</div> |
| | | <div v-if="employee.email">{{ employee.email }}</div> |
| | | </div> |
| | | </div> |
| | | </el-option> |
| | | </el-option-group> |
| | | </el-select> |
| | | <div style="margin-top: 8px; color: #909399; font-size: 12px;"> |
| | | 已鿩 {{ meetingForm.participants.length }} 人 |
| | | </div> |
| | | <!-- å·²éæ©äººå详æ
--> |
| | | <div v-if="meetingForm.participants.length > 0" style="margin-top: 10px;"> |
| | | <el-tag |
| | | v-for="participantId in meetingForm.participants" |
| | | :key="participantId" |
| | | closable |
| | | @close="removeParticipant(participantId)" |
| | | style="margin-right: 8px; margin-bottom: 8px;" |
| | | > |
| | | {{ getEmployeeName(participantId) }} |
| | | </el-tag> |
| | | </div> |
| | | </el-form-item> |
| | | <el-form-item label="ä¼è®®æè¿°" prop="description"> |
| | | <el-input |
| | | v-model="meetingForm.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥ä¼è®®æè¿°" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="meetingDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="createMeeting">å建ä¼è®®</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æä»¶å
±äº«å¼¹çª --> |
| | | <el-dialog |
| | | v-model="fileShareDialogVisible" |
| | | title="æä»¶å
񄧮" |
| | | width="700px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form ref="fileShareFormRef" :model="fileShareForm" :rules="fileShareRules" label-width="120px"> |
| | | <el-form-item label="å
±äº«æ é¢" prop="title"> |
| | | <el-input v-model="fileShareForm.title" placeholder="请è¾å
¥å
±äº«æ é¢" /> |
| | | </el-form-item> |
| | | <el-form-item label="å
±äº«æè¿°" prop="description"> |
| | | <el-input |
| | | v-model="fileShareForm.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å
±äº«æè¿°" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥æ¶é¨é¨" prop="departments"> |
| | | <el-select |
| | | v-model="fileShareForm.departments" |
| | | multiple |
| | | placeholder="è¯·éæ©æ¥æ¶é¨é¨" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="dept in departments" |
| | | :key="dept" |
| | | :label="dept" |
| | | :value="dept" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ä¸ä¼ æä»¶" prop="files"> |
| | | <el-upload |
| | | ref="uploadRef" |
| | | :auto-upload="false" |
| | | :on-change="handleFileChange" |
| | | :on-remove="removeFile" |
| | | :file-list="fileList" |
| | | multiple |
| | | :limit="10" |
| | | accept=".doc,.docx,.pdf,.xls,.xlsx,.ppt,.pptx,.txt,.jpg,.jpeg,.png,.gif" |
| | | > |
| | | <el-button type="primary">éæ©æä»¶</el-button> |
| | | <template #tip> |
| | | <div class="el-upload__tip"> |
| | | æ¯æä¸ä¼ ææ¡£ãå¾ççæ ¼å¼ï¼å个æä»¶ä¸è¶
è¿10MBï¼æå¤10个æä»¶ |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="fileShareDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="shareFiles">å
±äº«æä»¶</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs, computed } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/employeeRecord.js"; |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = { |
| | | title: [ |
| | | { required: true, message: "请è¾å
¥éç¥æ é¢", trigger: "blur" } |
| | | ], |
| | | type: [ |
| | | { required: true, message: "è¯·éæ©éç¥ç±»å", trigger: "change" } |
| | | ], |
| | | content: [ |
| | | { required: true, message: "请è¾å
¥éç¥å
容", trigger: "blur" } |
| | | ] |
| | | }; |
| | | |
| | | const meetingRules = { |
| | | title: [ |
| | | { required: true, message: "请è¾å
¥ä¼è®®æ é¢", trigger: "blur" } |
| | | ], |
| | | startTime: [ |
| | | { required: true, message: "è¯·éæ©ä¼è®®å¼å§æ¶é´", trigger: "change" } |
| | | ], |
| | | participants: [ |
| | | { required: true, message: "è¯·éæ©åä¼äººå", trigger: "change" } |
| | | ] |
| | | }; |
| | | |
| | | const fileShareRules = { |
| | | title: [ |
| | | { required: true, message: "请è¾å
¥å
±äº«æ é¢", trigger: "blur" } |
| | | ], |
| | | description: [ |
| | | { required: true, message: "请è¾å
¥å
±äº«æè¿°", trigger: "blur" } |
| | | ] |
| | | }; |
| | | |
| | | // ååºå¼æ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | title: "", |
| | | type: "", |
| | | status: "", |
| | | }, |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | | selectedIds: [], |
| | | // æ°å¢éç¥ç¸å
³ |
| | | form: { |
| | | title: "", |
| | | type: "", |
| | | priority: "medium", |
| | | content: "", |
| | | departments: [], |
| | | expireDate: "", |
| | | syncMethods: [] |
| | | }, |
| | | dialogVisible: false, |
| | | dialogTitle: "", |
| | | dialogType: "add", |
| | | // å¨çº¿ä¼è®®ç¸å
³ |
| | | meetingDialogVisible: false, |
| | | meetingForm: { |
| | | title: "", |
| | | startTime: "", |
| | | duration: 60, |
| | | participants: [], |
| | | description: "", |
| | | platform: "wechat" |
| | | }, |
| | | // æä»¶å
±äº«ç¸å
³ |
| | | fileShareDialogVisible: false, |
| | | fileShareForm: { |
| | | title: "", |
| | | description: "", |
| | | departments: [], |
| | | files: [] |
| | | }, |
| | | fileList: [] |
| | | }); |
| | | |
| | | const { |
| | | searchForm, |
| | | tableLoading, |
| | | page, |
| | | tableData, |
| | | selectedIds, |
| | | form, |
| | | dialogVisible, |
| | | dialogTitle, |
| | | dialogType, |
| | | meetingDialogVisible, |
| | | meetingForm, |
| | | fileShareDialogVisible, |
| | | fileShareForm, |
| | | fileList |
| | | } = toRefs(data); |
| | | |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(); |
| | | const meetingFormRef = ref(); |
| | | const fileShareFormRef = ref(); |
| | | |
| | | // è¡¨æ ¼åé
ç½® |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "éç¥æ é¢", |
| | | prop: "title", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "éç¥ç±»å", |
| | | prop: "type", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const typeMap = { |
| | | holiday: "æ¾åéç¥", |
| | | penalty: "å¤ç½éç¥", |
| | | meeting: "å¼ä¼éç¥", |
| | | temporary: "临æ¶éç¥", |
| | | formal: "æ£å¼éç¥" |
| | | }; |
| | | return typeMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | holiday: "success", |
| | | penalty: "danger", |
| | | meeting: "warning", |
| | | temporary: "info", |
| | | formal: "primary" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "ä¼å
级", |
| | | prop: "priority", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const priorityMap = { |
| | | low: "æ®é", |
| | | medium: "éè¦", |
| | | high: "ç´§æ¥" |
| | | }; |
| | | return priorityMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | low: "info", |
| | | medium: "warning", |
| | | high: "danger" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "status", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | const statusMap = { |
| | | draft: "è稿", |
| | | published: "å·²åå¸", |
| | | expired: "å·²è¿æ" |
| | | }; |
| | | return statusMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | draft: "info", |
| | | published: "success", |
| | | expired: "danger" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "æ¥æ¶é¨é¨", |
| | | prop: "departments", |
| | | width: 150, |
| | | showOverflowTooltip: true, |
| | | formatData: (params) => { |
| | | if (!params || params.length === 0) return "å
¨é¨é¨é¨"; |
| | | return params.join(", "); |
| | | } |
| | | }, |
| | | { |
| | | label: "æææè³", |
| | | prop: "expireDate", |
| | | width: 150, |
| | | formatData: (params) => { |
| | | if (!params) return "æ°¸ä¹
ææ"; |
| | | return params; |
| | | } |
| | | }, |
| | | { |
| | | label: "å建æ¶é´", |
| | | prop: "createTime", |
| | | width: 180, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 280, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | } |
| | | }, |
| | | { |
| | | name: "åå¸", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | publishNotification(row); |
| | | }, |
| | | // disabled: (row) => row.status === "published" |
| | | }, |
| | | { |
| | | name: "æ¤å", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | revokeNotification(row); |
| | | }, |
| | | // disabled: (row) => row.status !== "published" |
| | | } |
| | | ] |
| | | } |
| | | ]); |
| | | |
| | | // æ¨¡ææ°æ® |
| | | let mockData = [ |
| | | { |
| | | id: "1", |
| | | title: "2024å¹´æ¥èæ¾åéç¥", |
| | | type: "holiday", |
| | | priority: "high", |
| | | status: "published", |
| | | content: "æ ¹æ®å½å®¶è§å®ï¼ç»åå
¬å¸å®é
æ
åµï¼ç°å°2024å¹´æ¥èæ¾å宿éç¥å¦ä¸...", |
| | | departments: ["ææ¯é¨", "éå®é¨", "人äºé¨", "è´¢å¡é¨", "è¿è¥é¨", "å¸åºé¨", "客æé¨"], |
| | | expireDate: "2024-02-15", |
| | | syncMethods: ["wechat", "dingtalk", "email"], |
| | | createTime: "2024-01-15 10:30:00" |
| | | }, |
| | | { |
| | | id: "2", |
| | | title: "ææ¯é¨å¨ä¾ä¼éç¥", |
| | | type: "meeting", |
| | | priority: "medium", |
| | | status: "published", |
| | | content: "ææ¯é¨å®äºæ¯å¨äºä¸å2ç¹å¬å¼å¨ä¾ä¼ï¼è¯·åä½åäºåæ¶åå ...", |
| | | departments: ["ææ¯é¨"], |
| | | expireDate: "2024-01-20", |
| | | syncMethods: ["wechat", "dingtalk"], |
| | | createTime: "2024-01-14 15:20:00" |
| | | }, |
| | | { |
| | | id: "3", |
| | | title: "åå·¥è¡ä¸ºè§èå¤ç½éç¥", |
| | | type: "penalty", |
| | | priority: "high", |
| | | status: "draft", |
| | | content: "为维æ¤å
¬å¸æ£å¸¸ç§©åºï¼è§èåå·¥è¡ä¸ºï¼ç°å¯¹è¿åå
¬å¸è§å®çè¡ä¸ºè¿è¡å¤ç½...", |
| | | departments: ["人äºé¨", "ææ¯é¨", "éå®é¨"], |
| | | expireDate: "2024-02-13", |
| | | syncMethods: ["wechat", "email"], |
| | | createTime: "2024-01-13 09:15:00" |
| | | } |
| | | ]; |
| | | |
| | | // éç¥æ 颿¨¡æ¿ |
| | | const titleTemplates = [ |
| | | "å
³äº{year}å¹´{holiday}æ¾å宿çéç¥", |
| | | "{dept}é¨é¨{meeting}ä¼è®®éç¥", |
| | | "åå·¥{behavior}è¡ä¸ºè§èæé", |
| | | "{company}éè¦äºé¡¹éç¥", |
| | | "{dept}é¨é¨å·¥ä½å®æéç¥", |
| | | "å
³äº{project}项ç®è¿åº¦çéç¥", |
| | | "{dept}é¨é¨äººåè°æ´éç¥", |
| | | "å
¬å¸{policy}æ¿çæ´æ°éç¥" |
| | | ]; |
| | | |
| | | // éç¥ç±»åé
ç½® |
| | | const notificationTypes = [ |
| | | { type: "holiday", label: "æ¾åéç¥", priority: "high" }, |
| | | { type: "meeting", label: "å¼ä¼éç¥", priority: "medium" }, |
| | | { type: "penalty", label: "å¤ç½éç¥", priority: "high" }, |
| | | { type: "temporary", label: "临æ¶éç¥", priority: "low" }, |
| | | { type: "formal", label: "æ£å¼éç¥", priority: "medium" } |
| | | ]; |
| | | |
| | | // é¨é¨å表 |
| | | const departments = ["ææ¯é¨", "éå®é¨", "人äºé¨", "è´¢å¡é¨", "è¿è¥é¨", "å¸åºé¨", "客æé¨"]; |
| | | |
| | | // 人åå表 |
| | | const employees = ref([]); |
| | | const employeesLoading = ref(false); |
| | | |
| | | // è·åå¨èåå·¥å表 |
| | | const getEmployeesList = async () => { |
| | | try { |
| | | employeesLoading.value = true; |
| | | // ä¼å
使ç¨ç³»ç»ç¨æ·æ¥å£ï¼æç§æ·è·åï¼ |
| | | const userResponse = await userListNoPageByTenantId(); |
| | | |
| | | if (userResponse.data) { |
| | | employees.value = userResponse.data.map(user => ({ |
| | | label: user.nickName || user.userName || 'æªç¥å§å', |
| | | value: user.userId || user.id, |
| | | dept: user.dept?.deptName || 'æªç¥é¨é¨', |
| | | phone: user.phonenumber || '', |
| | | email: user.email || '', |
| | | status: user.status || '0' |
| | | })).filter(user => user.status === '0'); // åªæ¾ç¤ºæ£å¸¸ç¶æçç¨æ· |
| | | } else { |
| | | // å¦æç³»ç»ç¨æ·æ¥å£å¤±è´¥ï¼ä½¿ç¨åå·¥å°è´¦æ¥å£ |
| | | const response = await staffOnJobListPage({ |
| | | pageNum: 1, |
| | | pageSize: 1000, |
| | | staffState: 1 // å¨èç¶æ |
| | | }); |
| | | |
| | | if (response.data && response.data.records) { |
| | | employees.value = response.data.records.map(employee => ({ |
| | | label: employee.staffName || employee.name || 'æªç¥å§å', |
| | | value: employee.staffNo || employee.id || employee.staffId, |
| | | dept: employee.deptName || employee.department || 'æªç¥é¨é¨', |
| | | phone: employee.phone || employee.mobile || '', |
| | | email: employee.email || '', |
| | | status: '0' |
| | | })); |
| | | } |
| | | } |
| | | } catch (error) { |
| | | console.error('è·ååå·¥å表失败:', error); |
| | | // 妿æ¥å£é½å¤±è´¥ï¼ä½¿ç¨é»è®¤æ°æ® |
| | | employees.value = [ |
| | | { label: "å¼ ä¸", value: "001", dept: "ææ¯é¨", phone: "13800138001", email: "zhangsan@company.com", status: "0" }, |
| | | { label: "æå", value: "002", dept: "éå®é¨", phone: "13800138002", email: "lisi@company.com", status: "0" }, |
| | | { label: "çäº", value: "003", dept: "人äºé¨", phone: "13800138003", email: "wangwu@company.com", status: "0" } |
| | | ]; |
| | | } finally { |
| | | employeesLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // åå·¥åç» |
| | | const employeeGroups = computed(() => { |
| | | const groups = {}; |
| | | employees.value.forEach(employee => { |
| | | const dept = employee.dept || 'å
¶ä»é¨é¨'; |
| | | if (!groups[dept]) { |
| | | groups[dept] = []; |
| | | } |
| | | groups[dept].push(employee); |
| | | }); |
| | | |
| | | // æé¨é¨åç§°æåºï¼ç¡®ä¿æ¾ç¤ºé¡ºåºä¸è´ |
| | | return Object.keys(groups) |
| | | .sort() |
| | | .map(dept => ({ |
| | | label: dept, |
| | | options: groups[dept].sort((a, b) => a.label.localeCompare(b.label, 'zh-CN')) |
| | | })); |
| | | }); |
| | | |
| | | // è¿æ»¤åå·¥ï¼è¿ç¨æç´¢ï¼ |
| | | const filterEmployees = (query) => { |
| | | if (query !== '') { |
| | | const lowerQuery = query.toLowerCase(); |
| | | return employees.value.filter(employee => |
| | | employee.label.toLowerCase().includes(lowerQuery) || |
| | | employee.dept.toLowerCase().includes(lowerQuery) || |
| | | (employee.phone && employee.phone.includes(query)) || |
| | | (employee.email && employee.email.toLowerCase().includes(lowerQuery)) |
| | | ); |
| | | } else { |
| | | return employees.value; |
| | | } |
| | | }; |
| | | |
| | | // å·æ°åå·¥å表 |
| | | const refreshEmployees = async () => { |
| | | ElMessage.info("æ£å¨å·æ°åå·¥å表..."); |
| | | await getEmployeesList(); |
| | | |
| | | // ç»è®¡åé¨é¨äººæ° |
| | | const deptStats = {}; |
| | | employees.value.forEach(emp => { |
| | | const dept = emp.dept || 'å
¶ä»é¨é¨'; |
| | | deptStats[dept] = (deptStats[dept] || 0) + 1; |
| | | }); |
| | | |
| | | const deptInfo = Object.entries(deptStats) |
| | | .map(([dept, count]) => `${dept}: ${count}人`) |
| | | .join(', '); |
| | | |
| | | ElMessage.success(`åå·¥åè¡¨å·æ°å®æï¼å
± ${employees.value.length} 人 (${deptInfo})`); |
| | | }; |
| | | |
| | | // è·ååå·¥å§å |
| | | const getEmployeeName = (employeeId) => { |
| | | const employee = employees.value.find(emp => emp.value === employeeId); |
| | | return employee ? employee.label : 'æªç¥äººå'; |
| | | }; |
| | | |
| | | // è·åå工详ç»ä¿¡æ¯ |
| | | const getEmployeeInfo = (employeeId) => { |
| | | const employee = employees.value.find(emp => emp.value === employeeId); |
| | | if (!employee) return null; |
| | | |
| | | return { |
| | | name: employee.label, |
| | | dept: employee.dept, |
| | | phone: employee.phone, |
| | | email: employee.email |
| | | }; |
| | | }; |
| | | |
| | | // ç§»é¤åä¼äººå |
| | | const removeParticipant = (participantId) => { |
| | | const index = meetingForm.value.participants.indexOf(participantId); |
| | | if (index > -1) { |
| | | meetingForm.value.participants.splice(index, 1); |
| | | } |
| | | }; |
| | | |
| | | // 忥æ¹å¼é项 |
| | | const syncMethods = [ |
| | | { label: "ä¼ä¸å¾®ä¿¡", value: "wechat" }, |
| | | { label: "éé", value: "dingtalk" }, |
| | | { label: "é®ä»¶", value: "email" }, |
| | | { label: "çä¿¡", value: "sms" } |
| | | ]; |
| | | |
| | | // ä¼è®®å¹³å°é项 |
| | | const meetingPlatforms = [ |
| | | { label: "ä¼ä¸å¾®ä¿¡ä¼è®®", value: "wechat" }, |
| | | { label: "ééä¼è®®", value: "dingtalk" }, |
| | | { label: "è
¾è®¯ä¼è®®", value: "tencent" }, |
| | | { label: "Zoom", value: "zoom" } |
| | | ]; |
| | | |
| | | // èªå¨çææ°æ°æ® |
| | | const generateNewData = () => { |
| | | const newId = (mockData.length + 1).toString(); |
| | | const now = new Date(); |
| | | const randomType = notificationTypes[Math.floor(Math.random() * notificationTypes.length)]; |
| | | const randomDept = departments[Math.floor(Math.random() * departments.length)]; |
| | | |
| | | // çæéæºæ é¢ |
| | | let title = titleTemplates[Math.floor(Math.random() * titleTemplates.length)]; |
| | | title = title |
| | | .replace('{year}', now.getFullYear()) |
| | | .replace('{holiday}', ['æ¥è', 'å½åº', 'ä¸ç§', 'å
æ¦'][Math.floor(Math.random() * 4)]) |
| | | .replace('{dept}', randomDept) |
| | | .replace('{meeting}', ['å¨ä¾ä¼', 'æåº¦æ»ç»', '项ç®è¯å®¡', 'å¹è®ä¼è®®'][Math.floor(Math.random() * 4)]) |
| | | .replace('{behavior}', ['èå¤', 'çè£
', 'å·¥ä½æåº¦', 'å¢éåä½'][Math.floor(Math.random() * 4)]) |
| | | .replace('{company}', ['å
¬å¸', 'éå¢', 'æ»é¨'][Math.floor(Math.random() * 4)]) |
| | | .replace('{project}', ['æ°åå转å', '产åå级', 'å¸åºæå±', '人æå¹å
»'][Math.floor(Math.random() * 4)]) |
| | | .replace('{policy}', ['èå¤', 'èªé
¬', 'ç¦å©', 'æå'][Math.floor(Math.random() * 4)]); |
| | | |
| | | // éæºç¶æ |
| | | const statuses = ['draft', 'published']; |
| | | const randomStatus = statuses[Math.floor(Math.random() * statuses.length)]; |
| | | |
| | | // éæºä¼å
级 |
| | | const priorities = ['low', 'medium', 'high']; |
| | | const randomPriority = priorities[Math.floor(Math.random() * priorities.length)]; |
| | | |
| | | const newNotification = { |
| | | id: newId, |
| | | title: title, |
| | | type: randomType.type, |
| | | priority: randomPriority, |
| | | status: randomStatus, |
| | | content: `è¿æ¯${title}ç详ç»å
容ï¼è¯·ç¸å
³äººåæ³¨ææ¥ç...`, |
| | | departments: [randomDept], |
| | | expireDate: new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], // 30天åè¿æ |
| | | syncMethods: ["wechat", "dingtalk"], |
| | | createTime: now.toLocaleString() |
| | | }; |
| | | |
| | | // æ·»å å°æ°æ®å¼å¤´ |
| | | mockData.unshift(newNotification); |
| | | |
| | | // ä¿ææ°æ®éå¨åçèå´å
ï¼æå¤ä¿ç20æ¡ï¼ |
| | | if (mockData.length > 20) { |
| | | mockData = mockData.slice(0, 20); |
| | | } |
| | | |
| | | console.log(`[${new Date().toLocaleString()}] èªå¨çææ°éç¥: ${title}`); |
| | | }; |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | getList(); |
| | | getEmployeesList(); // è·ååå·¥å表 |
| | | startAutoRefresh(); |
| | | }); |
| | | |
| | | // å¼å§èªå¨å·æ° |
| | | const startAutoRefresh = () => { |
| | | setInterval(() => { |
| | | generateNewData(); |
| | | getList(); |
| | | }, 600000); // 10åéå·æ°ä¸æ¬¡ (10 * 60 * 1000 = 600000ms) |
| | | }; |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const handleQuery = () => { |
| | | page.value.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | |
| | | setTimeout(() => { |
| | | let filteredData = [...mockData]; |
| | | |
| | | if (searchForm.value.title) { |
| | | filteredData = filteredData.filter(item => |
| | | item.title.toLowerCase().includes(searchForm.value.title.toLowerCase()) |
| | | ); |
| | | } |
| | | |
| | | if (searchForm.value.type) { |
| | | filteredData = filteredData.filter(item => item.type === searchForm.value.type); |
| | | } |
| | | |
| | | tableData.value = filteredData; |
| | | page.value.total = filteredData.length; |
| | | tableLoading.value = false; |
| | | }, 500); |
| | | }; |
| | | |
| | | // å页å¤ç |
| | | const pagination = (obj) => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // éæ©ååå¤ç |
| | | const handleSelectionChange = (selection) => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | }; |
| | | |
| | | // æå¼è¡¨å |
| | | const openForm = (type, row = null) => { |
| | | dialogType.value = type; |
| | | if (type === "add") { |
| | | dialogTitle.value = "æ°å¢éç¥"; |
| | | // é置表å |
| | | Object.assign(form.value, { |
| | | title: "", |
| | | type: "", |
| | | priority: "medium", |
| | | content: "", |
| | | departments: [], |
| | | expireDate: "", |
| | | syncMethods: [] |
| | | }); |
| | | } else if (type === "edit" && row) { |
| | | dialogTitle.value = "ç¼è¾éç¥"; |
| | | Object.assign(form.value, { |
| | | title: row.title, |
| | | type: row.type, |
| | | priority: row.priority, |
| | | content: row.content || "", |
| | | departments: row.departments || [], |
| | | expireDate: row.expireDate || "", |
| | | syncMethods: row.syncMethods || [] |
| | | }); |
| | | } |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // æå¼å¨çº¿ä¼è®®å¼¹çª |
| | | const openMeetingDialog = () => { |
| | | // é置表å |
| | | Object.assign(meetingForm.value, { |
| | | title: "", |
| | | startTime: "", |
| | | duration: 60, |
| | | participants: [], |
| | | description: "", |
| | | platform: "wechat" |
| | | }); |
| | | meetingDialogVisible.value = true; |
| | | }; |
| | | |
| | | // æå¼æä»¶å
±äº«å¼¹çª |
| | | const openFileShareDialog = () => { |
| | | // é置表å |
| | | Object.assign(fileShareForm.value, { |
| | | title: "", |
| | | description: "", |
| | | departments: [], |
| | | files: [] |
| | | }); |
| | | fileList.value = []; |
| | | fileShareDialogVisible.value = true; |
| | | }; |
| | | |
| | | // æå¨å·æ°æ°æ® |
| | | const manualRefresh = () => { |
| | | generateNewData(); |
| | | getList(); |
| | | ElMessage.success("æå¨å·æ°å®æï¼å·²çææ°éç¥"); |
| | | }; |
| | | |
| | | // æäº¤éç¥è¡¨å |
| | | const submitForm = async () => { |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | if (dialogType.value === "add") { |
| | | // æ°å¢éç¥ |
| | | const newNotification = { |
| | | id: (mockData.length + 1).toString(), |
| | | title: form.value.title, |
| | | type: form.value.type, |
| | | priority: form.value.priority, |
| | | status: "draft", |
| | | content: form.value.content, |
| | | departments: form.value.departments, |
| | | expireDate: form.value.expireDate, |
| | | syncMethods: form.value.syncMethods, |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | |
| | | mockData.unshift(newNotification); |
| | | ElMessage.success("éç¥å建æå"); |
| | | } else { |
| | | // ç¼è¾éç¥ |
| | | const index = mockData.findIndex(item => item.id === selectedIds.value[0]); |
| | | if (index !== -1) { |
| | | Object.assign(mockData[index], { |
| | | title: form.value.title, |
| | | type: form.value.type, |
| | | priority: form.value.priority, |
| | | content: form.value.content, |
| | | departments: form.value.departments, |
| | | expireDate: form.value.expireDate, |
| | | syncMethods: form.value.syncMethods |
| | | }); |
| | | ElMessage.success("éç¥æ´æ°æå"); |
| | | } |
| | | } |
| | | |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } catch (error) { |
| | | console.error("表åéªè¯å¤±è´¥:", error); |
| | | } |
| | | }; |
| | | |
| | | // å建ä¼è®® |
| | | const createMeeting = async () => { |
| | | try { |
| | | await meetingFormRef.value.validate(); |
| | | |
| | | // 模æå建ä¼è®® |
| | | const meetingInfo = { |
| | | title: meetingForm.value.title, |
| | | startTime: meetingForm.value.startTime, |
| | | duration: meetingForm.value.duration, |
| | | participants: meetingForm.value.participants, |
| | | description: meetingForm.value.description, |
| | | platform: meetingForm.value.platform, |
| | | meetingId: `MTG${Date.now()}` |
| | | }; |
| | | |
| | | // 模æåéå°ä¼ä¸å¾®ä¿¡/éé |
| | | const platformName = meetingPlatforms.find(p => p.value === meetingForm.value.platform)?.label || "æªç¥å¹³å°"; |
| | | |
| | | ElMessage.success(`ä¼è®®å建æåï¼ä¼è®®ID: ${meetingInfo.meetingId}ï¼å°éè¿${platformName}åééç¥`); |
| | | meetingDialogVisible.value = false; |
| | | |
| | | // è·ååä¼äººåä¿¡æ¯ |
| | | const participantNames = meetingForm.value.participants.map(participantId => { |
| | | const employee = employees.value.find(emp => emp.value === participantId); |
| | | return employee ? employee.label : 'æªç¥äººå'; |
| | | }).join('ã'); |
| | | |
| | | // è·ååä¼äººå详ç»ä¿¡æ¯ |
| | | const participantDetails = meetingForm.value.participants.map(participantId => { |
| | | const employee = employees.value.find(emp => emp.value === participantId); |
| | | return employee ? { |
| | | name: employee.label, |
| | | dept: employee.dept, |
| | | phone: employee.phone, |
| | | email: employee.email |
| | | } : null; |
| | | }).filter(Boolean); |
| | | |
| | | // å°ä¼è®®ä¿¡æ¯æ·»å å°éç¥å表 |
| | | const meetingNotification = { |
| | | id: (mockData.length + 1).toString(), |
| | | title: `[ä¼è®®éç¥] ${meetingInfo.title}`, |
| | | type: "meeting", |
| | | priority: "high", |
| | | status: "published", |
| | | content: `ä¼è®®æ¶é´: ${meetingInfo.startTime}ï¼æ¶é¿: ${meetingInfo.duration}åéï¼å¹³å°: ${meetingPlatforms.find(p => p.value === meetingForm.value.platform)?.label || "æªç¥å¹³å°"}ï¼åä¼äººå: ${participantNames}ï¼å
±${participantDetails.length}人`, |
| | | departments: [], |
| | | expireDate: "", |
| | | syncMethods: [meetingForm.value.platform], |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | |
| | | mockData.unshift(meetingNotification); |
| | | getList(); |
| | | } catch (error) { |
| | | console.error("ä¼è®®è¡¨åéªè¯å¤±è´¥:", error); |
| | | } |
| | | }; |
| | | |
| | | // æä»¶ä¸ä¼ å¤ç |
| | | const handleFileChange = (file) => { |
| | | const isLt10M = file.size / 1024 / 1024 < 10; |
| | | if (!isLt10M) { |
| | | ElMessage.error("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶
è¿ 10MB!"); |
| | | return false; |
| | | } |
| | | |
| | | const fileInfo = { |
| | | name: file.name, |
| | | size: file.size, |
| | | type: file.type, |
| | | uid: file.uid |
| | | }; |
| | | |
| | | fileList.value.push(fileInfo); |
| | | fileShareForm.value.files.push(fileInfo); |
| | | return false; // 黿¢èªå¨ä¸ä¼ |
| | | }; |
| | | |
| | | // ç§»é¤æä»¶ |
| | | const removeFile = (file) => { |
| | | const index = fileList.value.findIndex(item => item.uid === file.uid); |
| | | if (index !== -1) { |
| | | const index2 = fileShareForm.value.files.findIndex(item => item.uid === file.uid); |
| | | if (index2 !== -1) { |
| | | fileShareForm.value.files.splice(index2, 1); |
| | | } |
| | | fileList.value.splice(index, 1); |
| | | } |
| | | }; |
| | | |
| | | // å
±äº«æä»¶ |
| | | const shareFiles = async () => { |
| | | try { |
| | | await fileShareFormRef.value.validate(); |
| | | |
| | | if (fileShareForm.value.files.length === 0) { |
| | | ElMessage.warning("请è³å°éæ©ä¸ä¸ªæä»¶"); |
| | | return; |
| | | } |
| | | |
| | | // 模ææä»¶å
񄧮 |
| | | const shareInfo = { |
| | | title: fileShareForm.value.title, |
| | | description: fileShareForm.value.description, |
| | | departments: fileShareForm.value.departments, |
| | | files: fileShareForm.value.files, |
| | | shareId: `FILE${Date.now()}` |
| | | }; |
| | | |
| | | ElMessage.success(`æä»¶å
±äº«æåï¼å
±äº«ID: ${shareInfo.shareId}ï¼å·²éç¥ç¸å
³é¨é¨`); |
| | | fileShareDialogVisible.value = false; |
| | | |
| | | // å°æä»¶å
±äº«ä¿¡æ¯æ·»å å°éç¥å表 |
| | | const fileShareNotification = { |
| | | id: (mockData.length + 1).toString(), |
| | | title: `[æä»¶å
񄧮] ${shareInfo.title}`, |
| | | type: "temporary", |
| | | priority: "medium", |
| | | status: "published", |
| | | content: `å
±äº«æè¿°: ${shareInfo.description}ï¼æä»¶æ°é: ${shareInfo.files.length}个`, |
| | | departments: shareInfo.departments, |
| | | expireDate: "", |
| | | syncMethods: ["wechat", "dingtalk"], |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | |
| | | mockData.unshift(fileShareNotification); |
| | | getList(); |
| | | } catch (error) { |
| | | console.error("æä»¶å
±äº«è¡¨åéªè¯å¤±è´¥:", error); |
| | | } |
| | | }; |
| | | |
| | | // åå¸éç¥ |
| | | const publishNotification = (row) => { |
| | | row.status = "published"; |
| | | ElMessage.success("éç¥å叿å"); |
| | | getList(); |
| | | }; |
| | | |
| | | // æ¤åéç¥ |
| | | const revokeNotification = (row) => { |
| | | row.status = "draft"; |
| | | ElMessage.success("éç¥å·²æ¤å"); |
| | | getList(); |
| | | }; |
| | | |
| | | // å é¤éç¥ |
| | | const handleDelete = () => { |
| | | if (selectedIds.value.length === 0) { |
| | | ElMessage.warning("è¯·éæ©è¦å é¤çéç¥"); |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | ElMessage.success("å 餿å"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .auto-refresh-info { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .auto-refresh-info .el-alert { |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | .el-upload__tip { |
| | | color: #909399; |
| | | font-size: 12px; |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .el-checkbox-group { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .el-checkbox { |
| | | margin-right: 0; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">ç¨åºåï¼</span> |
| | | <el-input |
| | | v-model="searchForm.programName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥ç¨åºåæç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span class="search_title ml10">æ§è¡ç¶æï¼</span> |
| | | <el-select v-model="searchForm.status" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="è¿è¡ä¸" :value="'running'" /> |
| | | <el-option label="已忢" :value="'stopped'" /> |
| | | <el-option label="å¼å¸¸" :value="'error'" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | æç´¢ |
| | | </el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | </div> |
| | | |
| | | <!-- RPA表åå¼¹çª --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="500px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="100px" |
| | | > |
| | | <el-form-item label="ç¨åºå" prop="programName"> |
| | | <el-input |
| | | v-model="form.programName" |
| | | placeholder="请è¾å
¥ç¨åºå" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="æ§è¡ç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©æ§è¡ç¶æ" style="width: 100%"> |
| | | <el-option label="è¿è¡ä¸" value="running" /> |
| | | <el-option label="已忢" value="stopped" /> |
| | | <el-option label="å¼å¸¸" value="error" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æè¿°" prop="description"> |
| | | <el-input |
| | | v-model="form.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥RPAç¨åºæè¿°" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | |
| | | // ååºå¼æ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | programName: "", |
| | | status: "", |
| | | }, |
| | | form: { |
| | | id: "", |
| | | programName: "", |
| | | status: "stopped", |
| | | description: "", |
| | | createTime: "", |
| | | }, |
| | | dialogVisible: false, |
| | | dialogTitle: "", |
| | | dialogType: "add", |
| | | selectedIds: [], |
| | | tableLoading: false, |
| | | page: { |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }, |
| | | tableData: [], |
| | | }); |
| | | |
| | | const { searchForm, form, dialogVisible, dialogTitle, dialogType, selectedIds, tableLoading, page, tableData } = toRefs(data); |
| | | |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(); |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = { |
| | | programName: [ |
| | | { required: true, message: "请è¾å
¥ç¨åºå", trigger: "blur" } |
| | | ], |
| | | status: [ |
| | | { required: true, message: "è¯·éæ©æ§è¡ç¶æ", trigger: "change" } |
| | | ] |
| | | }; |
| | | |
| | | // è¡¨æ ¼åé
ç½® |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ç¨åºå", |
| | | prop: "programName", |
| | | // width: 200, |
| | | }, |
| | | { |
| | | label: "æ§è¡ç¶æ", |
| | | prop: "status", |
| | | dataType: "tag", |
| | | // width: 120, |
| | | formatData: (params) => { |
| | | const statusMap = { |
| | | running: "è¿è¡ä¸", |
| | | stopped: "已忢", |
| | | error: "å¼å¸¸" |
| | | }; |
| | | return statusMap[params] || params; |
| | | }, |
| | | formatType: (params) => { |
| | | const typeMap = { |
| | | running: "success", |
| | | stopped: "info", |
| | | error: "danger" |
| | | }; |
| | | return typeMap[params] || "info"; |
| | | } |
| | | }, |
| | | { |
| | | label: "æè¿°", |
| | | prop: "description", |
| | | // width: 300, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "å建æ¶é´", |
| | | prop: "createTime", |
| | | // width: 180, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 230, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | } |
| | | }, |
| | | { |
| | | name: "å¼å§", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | handleStart(row); |
| | | }, |
| | | disabled: (row) => row.status !== 'stopped' |
| | | }, |
| | | { |
| | | name: "忢", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | handleStop(row); |
| | | }, |
| | | disabled: (row) => row.status === 'stopped' |
| | | } |
| | | ] |
| | | } |
| | | ]); |
| | | |
| | | // æ¨¡ææ°æ® |
| | | const mockData = [ |
| | | { |
| | | id: "1", |
| | | programName: "订åå¤çRPA", |
| | | status: "running", |
| | | description: "èªå¨å¤ç客æ·è®¢åï¼å
æ¬éªè¯ãåé
å确认", |
| | | createTime: "2024-01-15 10:30:00" |
| | | }, |
| | | { |
| | | id: "2", |
| | | programName: "åºå忥RPA", |
| | | status: "stopped", |
| | | description: "忥å¤ä¸ªä»åºçåºåæ°æ®ï¼ç¡®ä¿æ°æ®ä¸è´æ§", |
| | | createTime: "2024-01-14 15:20:00" |
| | | }, |
| | | { |
| | | id: "3", |
| | | programName: "æ¥è¡¨çæRPA", |
| | | status: "error", |
| | | description: "èªå¨çææ¯æ¥é宿¥è¡¨ååºåæ¥è¡¨", |
| | | createTime: "2024-01-13 09:15:00" |
| | | } |
| | | ]; |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const handleQuery = () => { |
| | | page.value.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | |
| | | // 模æAPIè°ç¨å»¶è¿ |
| | | setTimeout(() => { |
| | | let filteredData = [...mockData]; |
| | | |
| | | // æ ¹æ®æç´¢æ¡ä»¶è¿æ»¤æ°æ® |
| | | if (searchForm.value.programName) { |
| | | filteredData = filteredData.filter(item => |
| | | item.programName.toLowerCase().includes(searchForm.value.programName.toLowerCase()) |
| | | ); |
| | | } |
| | | |
| | | if (searchForm.value.status) { |
| | | filteredData = filteredData.filter(item => item.status === searchForm.value.status); |
| | | } |
| | | |
| | | tableData.value = filteredData; |
| | | page.value.total = filteredData.length; |
| | | tableLoading.value = false; |
| | | }, 500); |
| | | }; |
| | | |
| | | // å页å¤ç |
| | | const pagination = (obj) => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // éæ©ååå¤ç |
| | | const handleSelectionChange = (selection) => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | }; |
| | | |
| | | // æå¼è¡¨å |
| | | const openForm = (type, row) => { |
| | | dialogType.value = type; |
| | | dialogVisible.value = true; |
| | | |
| | | if (type === "add") { |
| | | dialogTitle.value = "æ·»å RPA"; |
| | | form.value = { |
| | | id: "", |
| | | programName: "", |
| | | status: "stopped", |
| | | description: "", |
| | | createTime: "", |
| | | }; |
| | | } else { |
| | | dialogTitle.value = "ç¼è¾RPA"; |
| | | form.value = { ...row }; |
| | | } |
| | | }; |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = async () => { |
| | | if (!formRef.value) return; |
| | | |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | if (dialogType.value === "add") { |
| | | // æ·»å æ°RPA |
| | | const newRPA = { |
| | | id: Date.now().toString(), |
| | | programName: form.value.programName, |
| | | status: form.value.status, |
| | | description: form.value.description, |
| | | createTime: new Date().toLocaleString(), |
| | | }; |
| | | |
| | | mockData.unshift(newRPA); |
| | | ElMessage.success("RPAæ·»å æå"); |
| | | } else { |
| | | // ç¼è¾RPA |
| | | const index = mockData.findIndex(item => item.id === form.value.id); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...form.value }; |
| | | ElMessage.success("RPAæ´æ°æå"); |
| | | } |
| | | } |
| | | |
| | | dialogVisible.value = false; |
| | | getList(); |
| | | } catch (error) { |
| | | console.error("表åéªè¯å¤±è´¥:", error); |
| | | } |
| | | }; |
| | | |
| | | // å¼å§RPA |
| | | const handleStart = (row) => { |
| | | ElMessageBox.confirm(`ç¡®å®è¦å¯å¨RPAç¨åº"${row.programName}"åï¼`, "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | row.status = "running"; |
| | | ElMessage.success("RPAå¯å¨æå"); |
| | | getList(); |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| | | }; |
| | | |
| | | // 忢RPA |
| | | const handleStop = (row) => { |
| | | ElMessageBox.confirm(`ç¡®å®è¦åæ¢RPAç¨åº"${row.programName}"åï¼`, "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | row.status = "stopped"; |
| | | ElMessage.success("RPA忢æå"); |
| | | getList(); |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| | | }; |
| | | |
| | | // å é¤RPA |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedIds.value.length > 0) { |
| | | ids = selectedIds.value.map((item) => item.id); |
| | | } else { |
| | | ElMessage.warning("è¯·éæ©è¦å é¤çRPA"); |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | // 仿¨¡ææ°æ®ä¸å é¤éä¸ç项 |
| | | ids.forEach(id => { |
| | | const index = mockData.findIndex(item => item.id === id); |
| | | if (index !== -1) { |
| | | mockData.splice(index, 1); |
| | | } |
| | | }); |
| | | |
| | | ElMessage.success("å 餿å"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped></style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="warning-system"> |
| | | <h2>é¢è¦è卿ºå¶</h2> |
| | | |
| | | <!-- ç»è®¡å¡ç --> |
| | | <div class="stats"> |
| | | <div class="stat-card red"> |
| | | <span class="number">2</span> |
| | | <span class="label">红è²é¢è¦</span> |
| | | </div> |
| | | <div class="stat-card orange"> |
| | | <span class="number">1</span> |
| | | <span class="label">æ©è²é¢è¦</span> |
| | | </div> |
| | | <div class="stat-card yellow"> |
| | | <span class="number">1</span> |
| | | <span class="label">é»è²é¢è¦</span> |
| | | </div> |
| | | <div class="stat-card green"> |
| | | <span class="number">1</span> |
| | | <span class="label">绿è²é¢è¦</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- é¢è¦å表 --> |
| | | <div class="warning-list"> |
| | | <h3>é¢è¦å表</h3> |
| | | <table> |
| | | <thead> |
| | | <tr> |
| | | <th>ç¼å·</th> |
| | | <th>æ é¢</th> |
| | | <th>ç±»å</th> |
| | | <th>ç级</th> |
| | | <th>ç¶æ</th> |
| | | <th>责任人</th> |
| | | <th>æä½</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr v-for="warning in warnings" :key="warning.id"> |
| | | <td>{{ warning.id }}</td> |
| | | <td>{{ warning.title }}</td> |
| | | <td>{{ warning.type }}</td> |
| | | <td> |
| | | <span :class="['level-tag', warning.level]"> |
| | | {{ warning.levelText }} |
| | | </span> |
| | | </td> |
| | | <td> |
| | | <span :class="['status-tag', warning.status]"> |
| | | {{ warning.statusText }} |
| | | </span> |
| | | </td> |
| | | <td>{{ warning.responsible }}</td> |
| | | <td> |
| | | <button @click="viewDetail(warning)">æ¥ç详æ
</button> |
| | | </td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | |
| | | <!-- 详æ
å¯¹è¯æ¡ --> |
| | | <div v-if="showDetail" class="modal"> |
| | | <div class="modal-content"> |
| | | <h3>é¢è¦è¯¦æ
</h3> |
| | | <div v-if="currentWarning"> |
| | | <p><strong>ç¼å·ï¼</strong>{{ currentWarning.id }}</p> |
| | | <p><strong>æ é¢ï¼</strong>{{ currentWarning.title }}</p> |
| | | <p><strong>ç±»åï¼</strong>{{ currentWarning.type }}</p> |
| | | <p><strong>ç级ï¼</strong>{{ currentWarning.levelText }}</p> |
| | | <p><strong>æè¿°ï¼</strong>{{ currentWarning.description }}</p> |
| | | <p><strong>å½±åï¼</strong>{{ currentWarning.impact }}</p> |
| | | <p><strong>建议ï¼</strong>{{ currentWarning.suggestions }}</p> |
| | | </div> |
| | | <button @click="showDetail = false">å
³é</button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | name: 'WarningSystem', |
| | | data() { |
| | | return { |
| | | showDetail: false, |
| | | currentWarning: null, |
| | | warnings: [ |
| | | { |
| | | id: 'W001', |
| | | title: '项ç®é¢ç®è¶
æ¯é¢è¦', |
| | | type: 'è´¢å¡é¢è¦', |
| | | level: 'red', |
| | | levelText: '红è²é¢è¦', |
| | | status: 'pending', |
| | | statusText: 'å¾
å¤ç', |
| | | responsible: 'å¼ ç»ç', |
| | | description: 'A项ç®é¢ç®æ§è¡ç已达95%ï¼é¢è®¡å°è¶
åºé¢ç®èå´ã', |
| | | impact: 'å½±åé¡¹ç®æ´ä½è´¢å¡ææ ï¼å¯è½å¯¼è´é¡¹ç®äºæ', |
| | | suggestions: 'æåéå¿
è¦æ¯åºï¼ä¼åèµæºé
ç½®ï¼ç³è¯·é¢ç®è°æ´' |
| | | }, |
| | | { |
| | | id: 'W002', |
| | | title: 'ååå°æé¢è¦', |
| | | type: 'åè§é¢è¦', |
| | | level: 'orange', |
| | | levelText: 'æ©è²é¢è¦', |
| | | status: 'processing', |
| | | statusText: 'å¤çä¸', |
| | | responsible: 'æä¸»ç®¡', |
| | | description: 'ä¸ä¾åºåBçååå°äº2024å¹´1æ25æ¥å°æã', |
| | | impact: 'å½±åä¾åºé¾ç¨³å®æ§ï¼å¯è½å¯¼è´æå¡ä¸æ', |
| | | suggestions: 'è¯ä¼°ä¾åºå表ç°ï¼åå¤ç»ç¾ææï¼å¶å®å¤éæ¹æ¡' |
| | | }, |
| | | { |
| | | id: 'W003', |
| | | title: '设å¤ç»´æ¤é¢è¦', |
| | | type: 'è¿è¥é¢è¦', |
| | | level: 'yellow', |
| | | levelText: 'é»è²é¢è¦', |
| | | status: 'pending', |
| | | statusText: 'å¾
å¤ç', |
| | | responsible: 'çå·¥ç¨å¸', |
| | | description: 'ç产线设å¤Cå·²è¿è¡8000å°æ¶ï¼æ¥è¿ç»´æ¤å¨æã', |
| | | impact: 'å¯è½å½±åç产æçå产åè´¨é', |
| | | suggestions: 'å®æç»´æ¤æ¶é´ï¼åå¤å¤ä»¶ï¼å¶å®ç»´æ¤è®¡å' |
| | | }, |
| | | { |
| | | id: 'W004', |
| | | title: '人åé
ç½®é¢è¦', |
| | | type: 'è¿è¥é¢è¦', |
| | | level: 'green', |
| | | levelText: '绿è²é¢è¦', |
| | | status: 'resolved', |
| | | statusText: '已解å³', |
| | | responsible: 'èµµHR', |
| | | description: 'ææ¯é¨é¨äººåé
ç½®å
è¶³ï¼é¡¹ç®è¿åº¦æ£å¸¸ã', |
| | | impact: 'æ è´é¢å½±å', |
| | | suggestions: 'ç»§ç»çæ§äººåé
ç½®æ
åµ' |
| | | }, |
| | | { |
| | | id: 'W005', |
| | | title: 'è´¨éäºæ
é¢è¦', |
| | | type: 'è¿è¥é¢è¦', |
| | | level: 'red', |
| | | levelText: '红è²é¢è¦', |
| | | status: 'pending', |
| | | statusText: 'å¾
å¤ç', |
| | | responsible: 'éæ»ç', |
| | | description: '产åDå¨å®¢æ·ç°åºåºç°è´¨éé®é¢ã', |
| | | impact: 'å½±åå®¢æ·æ»¡æåº¦ï¼å¯è½é æç»æµæå¤±', |
| | | suggestions: 'ç«å³å¬åé®é¢äº§åï¼åæåå ï¼å¶å®æ¹è¿æªæ½' |
| | | } |
| | | ] |
| | | } |
| | | }, |
| | | methods: { |
| | | viewDetail(warning) { |
| | | this.currentWarning = warning |
| | | this.showDetail = true |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .warning-system { |
| | | padding: 20px; |
| | | max-width: 1200px; |
| | | margin: 0 auto; |
| | | } |
| | | |
| | | h2 { |
| | | color: #333; |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .stats { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
| | | gap: 20px; |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .stat-card { |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | color: white; |
| | | text-align: center; |
| | | box-shadow: 0 2px 8px rgba(0,0,0,0.1); |
| | | } |
| | | |
| | | .stat-card.red { background: linear-gradient(135deg, #ff6b6b, #ee5a52); } |
| | | .stat-card.orange { background: linear-gradient(135deg, #ffa726, #ff9800); } |
| | | .stat-card.yellow { background: linear-gradient(135deg, #ffd54f, #ffc107); } |
| | | .stat-card.green { background: linear-gradient(135deg, #66bb6a, #4caf50); } |
| | | |
| | | .stat-card .number { |
| | | display: block; |
| | | font-size: 32px; |
| | | font-weight: bold; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .stat-card .label { |
| | | font-size: 14px; |
| | | opacity: 0.9; |
| | | } |
| | | |
| | | .warning-list h3 { |
| | | margin-bottom: 20px; |
| | | color: #333; |
| | | } |
| | | |
| | | table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | background: white; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | box-shadow: 0 2px 8px rgba(0,0,0,0.1); |
| | | } |
| | | |
| | | th, td { |
| | | padding: 12px; |
| | | text-align: left; |
| | | border-bottom: 1px solid #eee; |
| | | } |
| | | |
| | | th { |
| | | background: #f8f9fa; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .level-tag, .status-tag { |
| | | padding: 4px 8px; |
| | | border-radius: 4px; |
| | | font-size: 12px; |
| | | color: white; |
| | | } |
| | | |
| | | .level-tag.red { background: #f56c6c; } |
| | | .level-tag.orange { background: #e6a23c; } |
| | | .level-tag.yellow { background: #e6a23c; } |
| | | .level-tag.green { background: #67c23a; } |
| | | |
| | | .status-tag.pending { background: #f56c6c; } |
| | | .status-tag.processing { background: #e6a23c; } |
| | | .status-tag.resolved { background: #67c23a; } |
| | | |
| | | button { |
| | | padding: 6px 12px; |
| | | margin: 0 4px; |
| | | border: none; |
| | | border-radius: 4px; |
| | | cursor: pointer; |
| | | font-size: 12px; |
| | | background: #409eff; |
| | | color: white; |
| | | } |
| | | |
| | | .modal { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | height: 100%; |
| | | background: rgba(0,0,0,0.5); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .modal-content { |
| | | background: white; |
| | | padding: 30px; |
| | | border-radius: 8px; |
| | | max-width: 600px; |
| | | width: 90%; |
| | | max-height: 80vh; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .modal-content h3 { |
| | | margin-bottom: 20px; |
| | | color: #333; |
| | | } |
| | | |
| | | .modal-content p { |
| | | margin-bottom: 15px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .modal-content strong { |
| | | color: #333; |
| | | } |
| | | |
| | | .modal-content button { |
| | | background: #409eff; |
| | | color: white; |
| | | padding: 10px 20px; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card shadow="never"> |
| | | <div class="toolbar"> |
| | | <el-input |
| | | v-model="query.keyword" |
| | | placeholder="æç´¢åç§°/ç±»å«" |
| | | clearable |
| | | style="width: 240px" |
| | | @keyup.enter="handleSearch" |
| | | /> |
| | | <el-select |
| | | v-model="query.status" |
| | | placeholder="ç¶æ" |
| | | clearable |
| | | style="width: 140px; margin-left: 12px" |
| | | > |
| | | <el-option label="å¯ç¨" value="å¯ç¨" /> |
| | | <el-option label="åç¨" value="åç¨" /> |
| | | </el-select> |
| | | <el-button type="primary" style="margin-left: 12px" @click="handleSearch">æ¥è¯¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | <el-button type="success" plain style="float: right" @click="openCreate">æ°å¢</el-button> |
| | | </div> |
| | | |
| | | <el-table :data="pagedList" border style="width: 100%" height="480" stripe> |
| | | <el-table-column prop="id" label="ç¼å·" width="90" sortable /> |
| | | <el-table-column prop="name" label="åç§°" min-width="140" /> |
| | | <el-table-column prop="category" label="ç±»å«" width="120" /> |
| | | <el-table-column prop="stock" label="åºå" width="100" sortable /> |
| | | <el-table-column prop="price" label="åä»·(Â¥)" width="120"> |
| | | <template #default="scope">{{ formatPrice(scope.row.price) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="ç¶æ" width="120"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'å¯ç¨' ? 'success' : 'info'">{{ scope.row.status }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="updatedAt" label="æ´æ°æ¶é´" min-width="160" /> |
| | | <el-table-column label="æä½" width="180" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="openEdit(scope.row)">ç¼è¾</el-button> |
| | | <el-button link type="danger" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <div class="pagination"> |
| | | <el-pagination |
| | | background |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :total="filteredList.length" |
| | | :page-sizes="[5, 10, 20, 50]" |
| | | :page-size="pager.pageSize" |
| | | :current-page="pager.pageNum" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handleCurrentChange" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <el-dialog v-model="dialogVisible" :title="isEdit ? 'ç¼è¾' : 'æ°å¢'" width="520px"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="90px"> |
| | | <el-form-item label="åç§°" prop="name"> |
| | | <el-input v-model="form.name" placeholder="请è¾å
¥åç§°" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç±»å«" prop="category"> |
| | | <el-select v-model="form.category" placeholder="è¯·éæ©ç±»å«" style="width: 100%"> |
| | | <el-option label="åæ" value="åæ" /> |
| | | <el-option label="åæå" value="åæå" /> |
| | | <el-option label="æå" value="æå" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="åºå" prop="stock"> |
| | | <el-input v-model.number="form.stock" type="number" min="0" /> |
| | | </el-form-item> |
| | | <el-form-item label="åä»·(Â¥)" prop="price"> |
| | | <el-input v-model.number="form.price" type="number" min="0" step="0.01" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-radio-group v-model="form.status"> |
| | | <el-radio label="å¯ç¨">å¯ç¨</el-radio> |
| | | <el-radio label="åç¨">åç¨</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="dialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡® å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, nextTick } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | defineOptions({ name: 'FakePage' }) |
| | | |
| | | const query = reactive({ |
| | | keyword: '', |
| | | status: '' |
| | | }) |
| | | |
| | | const pager = reactive({ |
| | | pageNum: 1, |
| | | pageSize: 10 |
| | | }) |
| | | |
| | | const allList = ref(generateMockData()) |
| | | |
| | | const filteredList = computed(() => { |
| | | const keyword = (query.keyword || '').trim() |
| | | const status = query.status |
| | | return allList.value.filter(item => { |
| | | const hitKeyword = !keyword || item.name.includes(keyword) || item.category.includes(keyword) |
| | | const hitStatus = !status || item.status === status |
| | | return hitKeyword && hitStatus |
| | | }) |
| | | }) |
| | | |
| | | const pagedList = computed(() => { |
| | | const start = (pager.pageNum - 1) * pager.pageSize |
| | | const end = start + pager.pageSize |
| | | return filteredList.value.slice(start, end) |
| | | }) |
| | | |
| | | function handleSearch() { |
| | | pager.pageNum = 1 |
| | | } |
| | | |
| | | function resetQuery() { |
| | | query.keyword = '' |
| | | query.status = '' |
| | | pager.pageNum = 1 |
| | | } |
| | | |
| | | function handleSizeChange(size) { |
| | | pager.pageSize = size |
| | | pager.pageNum = 1 |
| | | } |
| | | |
| | | function handleCurrentChange(page) { |
| | | pager.pageNum = page |
| | | } |
| | | |
| | | function formatPrice(val) { |
| | | return Number(val || 0).toFixed(2) |
| | | } |
| | | |
| | | // æ°å¢/ç¼è¾ |
| | | const dialogVisible = ref(false) |
| | | const isEdit = ref(false) |
| | | const formRef = ref() |
| | | const form = reactive({ id: null, name: '', category: '', stock: 0, price: 0, status: 'å¯ç¨' }) |
| | | |
| | | const rules = { |
| | | name: [{ required: true, message: '请è¾å
¥åç§°', trigger: 'blur' }], |
| | | category: [{ required: true, message: 'è¯·éæ©ç±»å«', trigger: 'change' }], |
| | | stock: [{ required: true, message: '请è¾å
¥åºå', trigger: 'blur' }], |
| | | price: [{ required: true, message: '请è¾å
¥åä»·', trigger: 'blur' }] |
| | | } |
| | | |
| | | function openCreate() { |
| | | isEdit.value = false |
| | | Object.assign(form, { id: null, name: '', category: '', stock: 0, price: 0, status: 'å¯ç¨' }) |
| | | dialogVisible.value = true |
| | | nextTick(() => formRef.value?.clearValidate?.()) |
| | | } |
| | | |
| | | function openEdit(row) { |
| | | isEdit.value = true |
| | | Object.assign(form, JSON.parse(JSON.stringify(row))) |
| | | dialogVisible.value = true |
| | | nextTick(() => formRef.value?.clearValidate?.()) |
| | | } |
| | | |
| | | function submitForm() { |
| | | formRef.value?.validate?.((valid) => { |
| | | if (!valid) return |
| | | if (isEdit.value) { |
| | | const index = allList.value.findIndex(x => x.id === form.id) |
| | | if (index > -1) { |
| | | allList.value[index] = { ...form, updatedAt: nowString() } |
| | | ElMessage.success('å·²ä¿å') |
| | | } |
| | | } else { |
| | | const newId = Date.now() |
| | | allList.value.unshift({ ...form, id: newId, updatedAt: nowString() }) |
| | | ElMessage.success('å·²æ°å¢') |
| | | } |
| | | dialogVisible.value = false |
| | | }) |
| | | } |
| | | |
| | | function handleDelete(row) { |
| | | ElMessageBox.confirm(`确认å é¤ã${row.name}ãåï¼`, 'æç¤º', { type: 'warning' }) |
| | | .then(() => { |
| | | allList.value = allList.value.filter(x => x.id !== row.id) |
| | | ElMessage.success('å·²å é¤') |
| | | }) |
| | | .catch(() => {}) |
| | | } |
| | | |
| | | function generateMockData() { |
| | | const categories = ['åæ', 'åæå', 'æå'] |
| | | const statusOptions = ['å¯ç¨', 'åç¨'] |
| | | const list = [] |
| | | for (let i = 1; i <= 36; i++) { |
| | | list.push({ |
| | | id: i, |
| | | name: `ç©æ-${i.toString().padStart(3, '0')}`, |
| | | category: categories[i % categories.length], |
| | | stock: Math.floor(Math.random() * 1000), |
| | | price: (Math.random() * 500 + 10).toFixed(2), |
| | | status: statusOptions[i % 2], |
| | | updatedAt: nowString() |
| | | }) |
| | | } |
| | | return list |
| | | } |
| | | |
| | | function nowString() { |
| | | const d = new Date() |
| | | const yyyy = d.getFullYear() |
| | | const MM = String(d.getMonth() + 1).padStart(2, '0') |
| | | const dd = String(d.getDate()).padStart(2, '0') |
| | | const hh = String(d.getHours()).padStart(2, '0') |
| | | const mm = String(d.getMinutes()).padStart(2, '0') |
| | | const ss = String(d.getSeconds()).padStart(2, '0') |
| | | return `${yyyy}-${MM}-${dd} ${hh}:${mm}:${ss}` |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .toolbar { |
| | | margin-bottom: 12px; |
| | | } |
| | | .pagination { |
| | | margin-top: 12px; |
| | | text-align: right; |
| | | } |
| | | </style> |
| | | |
| | | |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- è¾¹ç¼è®¡ç®ç¶æçæ§ --> |
| | | <el-row :gutter="20" class="status-section"> |
| | | <el-col :span="8"> |
| | | <el-card class="status-card"> |
| | | <div class="status-item"> |
| | | <div class="status-icon"> |
| | | <el-icon><Monitor /></el-icon> |
| | | </div> |
| | | <div class="status-info"> |
| | | <div class="status-title">è¾¹ç¼æå¡å¨ç¶æ</div> |
| | | <div class="status-value" :class="edgeServerStatus.status"> |
| | | {{ edgeServerStatus.status === 'online' ? 'å¨çº¿' : '离线' }} |
| | | </div> |
| | | <div class="status-detail">æåå¿è·³: {{ formatTime(edgeServerStatus.lastHeartbeat) }}</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-card class="status-card"> |
| | | <div class="status-item"> |
| | | <div class="status-icon"> |
| | | <el-icon><Cpu /></el-icon> |
| | | </div> |
| | | <div class="status-info"> |
| | | <div class="status-title">模åè¿è¡ç¶æ</div> |
| | | <div class="status-value" :class="modelStatus.status"> |
| | | {{ modelStatus.status === 'running' ? 'è¿è¡ä¸' : '已忢' }} |
| | | </div> |
| | | <div class="status-detail">è¿è¡æ¨¡å: {{ modelStatus.modelCount }}个</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-card class="status-card"> |
| | | <div class="status-item"> |
| | | <div class="status-icon"> |
| | | <el-icon><TrendCharts /></el-icon> |
| | | </div> |
| | | <div class="status-info"> |
| | | <div class="status-title">èè½ææ</div> |
| | | <div class="status-value success">{{ energySavingRate.toFixed(1) }}%</div> |
| | | <div class="status-detail">累计èè½: {{ totalEnergySaved.toFixed(1) }}kWh</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 注水泵é¢çä¼åæ§å¶ --> |
| | | <el-card class="control-section"> |
| | | <template #header> |
| | | <span>注水泵é¢çä¼åæ§å¶</span> |
| | | </template> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="pump-control"> |
| | | <h4>宿¶åæ°çæ§</h4> |
| | | <el-form label-width="120px"> |
| | | <el-form-item label="å°å±åå (MPa)"> |
| | | <el-input v-model="pumpData.formationPressure" readonly> |
| | | <template #append>MPa</template> |
| | | </el-input> |
| | | </el-form-item> |
| | | <el-form-item label="å½åæ³µé (Hz)"> |
| | | <el-input v-model="pumpData.currentFrequency" readonly> |
| | | <template #append>Hz</template> |
| | | </el-input> |
| | | </el-form-item> |
| | | <el-form-item label="ä¼ååæ³µé (Hz)"> |
| | | <el-input v-model="pumpData.optimizedFrequency" readonly> |
| | | <template #append>Hz</template> |
| | | </el-input> |
| | | </el-form-item> |
| | | <el-form-item label="è½èéä½"> |
| | | <el-progress |
| | | :percentage="pumpData.energyReduction" |
| | | :color="getProgressColor" |
| | | :format="format => `${format}%`" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="æµé (m³/h)"> |
| | | <el-input v-model="pumpData.flowRate" readonly> |
| | | <template #append>m³/h</template> |
| | | </el-input> |
| | | </el-form-item> |
| | | <el-form-item label="åç (kW)"> |
| | | <el-input v-model="pumpData.power" readonly> |
| | | <template #append>kW</template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | </el-col> |
| | | |
| | | <el-col :span="12"> |
| | | <div class="pump-chart"> |
| | | <h4>é¢çä¼åè¶å¿</h4> |
| | | <div ref="frequencyChart" style="height: 300px;"></div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20" class="control-buttons"> |
| | | <el-col :span="24"> |
| | | <el-button |
| | | type="primary" |
| | | :disabled="!canControl" |
| | | @click="applyOptimization" |
| | | > |
| | | åºç¨ä¼å设置 |
| | | </el-button> |
| | | <el-button |
| | | type="warning" |
| | | :disabled="!canControl" |
| | | @click="emergencyStop" |
| | | > |
| | | ç´§æ¥åæ¢ |
| | | </el-button> |
| | | <el-button |
| | | type="info" |
| | | @click="showOptimizationHistory" |
| | | > |
| | | ä¼ååå² |
| | | </el-button> |
| | | <el-button |
| | | type="success" |
| | | @click="toggleAutoRefresh" |
| | | > |
| | | {{ autoRefreshStatus ? '忢èªå¨å·æ°' : 'å¼å¯èªå¨å·æ°' }} |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | </el-card> |
| | | |
| | | <!-- è¾¹ç¼è®¡ç®æ¨¡åé
ç½® --> |
| | | <el-card class="model-section"> |
| | | <template #header> |
| | | <span>è¾¹ç¼è®¡ç®æ¨¡åé
ç½®</span> |
| | | </template> |
| | | |
| | | <el-table :data="modelConfigs" style="width: 100%" stripe> |
| | | <el-table-column prop="modelName" label="模ååç§°" /> |
| | | <el-table-column prop="version" label="çæ¬" /> |
| | | <el-table-column prop="status" label="ç¶æ"> |
| | | <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="accuracy" label="åç¡®ç" /> |
| | | <el-table-column prop="lastUpdate" label="æåæ´æ°" /> |
| | | <el-table-column label="æä½"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | size="small" |
| | | @click="updateModel(scope.row)" |
| | | > |
| | | æ´æ°æ¨¡å |
| | | </el-button> |
| | | <el-button |
| | | size="small" |
| | | type="danger" |
| | | @click="deleteModel(scope.row)" |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <!-- è½èåæå¾è¡¨ --> |
| | | <el-card class="analysis-section"> |
| | | <template #header> |
| | | <span>è½èåæ</span> |
| | | </template> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div ref="energyChart" style="height: 400px;"></div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div ref="savingChart" style="height: 400px;"></div> |
| | | </el-col> |
| | | </el-row> |
| | | </el-card> |
| | | |
| | | <!-- ä¼ååå²å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="historyDialogVisible" title="ä¼ååå²è®°å½" width="80%"> |
| | | <el-table :data="optimizationHistory" style="width: 100%" stripe> |
| | | <el-table-column prop="timestamp" label="æ¶é´" /> |
| | | <el-table-column prop="formationPressure" label="å°å±åå (MPa)" /> |
| | | <el-table-column prop="oldFrequency" label="åé¢ç (Hz)" /> |
| | | <el-table-column prop="newFrequency" label="æ°é¢ç (Hz)" /> |
| | | <el-table-column prop="energySaved" label="èè½ (kWh)" /> |
| | | <el-table-column prop="status" label="ç¶æ"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'success' ? 'success' : 'warning'"> |
| | | {{ scope.row.status === 'success' ? 'æå' : '失败' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onUnmounted, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Monitor, Cpu, TrendCharts } from '@element-plus/icons-vue' |
| | | import * as echarts from 'echarts' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const edgeServerStatus = ref({ status: 'online', lastHeartbeat: Date.now() }) |
| | | const modelStatus = ref({ status: 'running', modelCount: 3 }) |
| | | const energySavingRate = ref(15.8) |
| | | const totalEnergySaved = ref(1250.5) |
| | | const pumpData = ref({ |
| | | formationPressure: 25.6, |
| | | currentFrequency: 45.2, |
| | | optimizedFrequency: 42.1, |
| | | energyReduction: 23, |
| | | flowRate: 180.5, |
| | | power: 85.3 |
| | | }) |
| | | |
| | | const modelConfigs = ref([ |
| | | { |
| | | modelName: '注水泵é¢çä¼å模å', |
| | | version: 'v2.1.0', |
| | | status: 'active', |
| | | accuracy: '94.2%', |
| | | lastUpdate: '2024-01-15 14:30:00' |
| | | }, |
| | | { |
| | | modelName: 'å°å±åå颿µæ¨¡å', |
| | | version: 'v1.8.5', |
| | | status: 'active', |
| | | accuracy: '91.7%', |
| | | lastUpdate: '2024-01-14 09:15:00' |
| | | }, |
| | | { |
| | | modelName: 'è½èåææ¨¡å', |
| | | version: 'v2.0.3', |
| | | status: 'standby', |
| | | accuracy: '89.3%', |
| | | lastUpdate: '2024-01-13 16:45:00' |
| | | } |
| | | ]) |
| | | |
| | | const historyDialogVisible = ref(false) |
| | | const optimizationHistory = ref([]) |
| | | |
| | | // å¾è¡¨å¼ç¨ |
| | | const frequencyChart = ref(null) |
| | | const energyChart = ref(null) |
| | | const savingChart = ref(null) |
| | | |
| | | // èªå¨å·æ°ç¸å
³ |
| | | const autoRefreshStatus = ref(true) |
| | | const autoRefreshTimer = ref(null) |
| | | const chartInstances = ref([]) |
| | | |
| | | // 计ç®å±æ§ |
| | | const canControl = computed(() => { |
| | | return edgeServerStatus.value.status === 'online' && modelStatus.value.status === 'running' |
| | | }) |
| | | |
| | | const getProgressColor = computed(() => { |
| | | return (percentage) => { |
| | | if (percentage < 20) return '#909399' |
| | | if (percentage < 40) return '#E6A23C' |
| | | if (percentage < 60) return '#409EFF' |
| | | return '#67C23A' |
| | | } |
| | | }) |
| | | |
| | | // çææ¨¡ææ°æ® |
| | | const generateMockData = () => { |
| | | // çæéæºå°å±åå (20-30 MPa) |
| | | const formationPressure = 20 + Math.random() * 10 |
| | | |
| | | // æ ¹æ®å°å±åå计ç®ä¼åé¢ç |
| | | const baseFrequency = 40 + (formationPressure - 25) * 2 |
| | | const currentFrequency = baseFrequency + (Math.random() - 0.5) * 4 |
| | | const optimizedFrequency = Math.max(35, baseFrequency - Math.random() * 3) |
| | | |
| | | // 计ç®è½èéä½ |
| | | const energyReduction = Math.round((currentFrequency - optimizedFrequency) / currentFrequency * 100) |
| | | |
| | | // è®¡ç®æµéååç |
| | | const flowRate = 150 + Math.random() * 60 |
| | | const power = 70 + Math.random() * 30 |
| | | |
| | | // æ´æ°æ³µæ°æ® |
| | | pumpData.value = { |
| | | formationPressure: parseFloat(formationPressure.toFixed(1)), |
| | | currentFrequency: parseFloat(currentFrequency.toFixed(1)), |
| | | optimizedFrequency: parseFloat(optimizedFrequency.toFixed(1)), |
| | | energyReduction: Math.min(energyReduction, 35), |
| | | flowRate: parseFloat(flowRate.toFixed(1)), |
| | | power: parseFloat(power.toFixed(1)) |
| | | } |
| | | |
| | | // æ´æ°èè½ææ |
| | | energySavingRate.value = 12 + Math.random() * 8 |
| | | totalEnergySaved.value += Math.random() * 2 |
| | | |
| | | // æ´æ°è¾¹ç¼æå¡å¨ç¶æ |
| | | edgeServerStatus.value.lastHeartbeat = Date.now() |
| | | |
| | | // éæºæ´æ°æ¨¡åç¶æ |
| | | if (Math.random() > 0.95) { |
| | | modelStatus.value.modelCount = Math.max(1, modelStatus.value.modelCount + (Math.random() > 0.5 ? 1 : -1)) |
| | | } |
| | | |
| | | // æ·»å ä¼ååå²è®°å½ |
| | | if (Math.random() > 0.7) { |
| | | addOptimizationHistory() |
| | | } |
| | | |
| | | // æ´æ°å¾è¡¨æ°æ® |
| | | updateCharts() |
| | | } |
| | | |
| | | // æ·»å ä¼ååå²è®°å½ |
| | | const addOptimizationHistory = () => { |
| | | const timestamp = new Date().toLocaleString() |
| | | const record = { |
| | | timestamp, |
| | | formationPressure: pumpData.value.formationPressure, |
| | | oldFrequency: pumpData.value.currentFrequency, |
| | | newFrequency: pumpData.value.optimizedFrequency, |
| | | energySaved: parseFloat((Math.random() * 5 + 1).toFixed(2)), |
| | | status: Math.random() > 0.1 ? 'success' : 'failed' |
| | | } |
| | | |
| | | optimizationHistory.value.unshift(record) |
| | | |
| | | // ä¿ææå¤100æ¡è®°å½ |
| | | if (optimizationHistory.value.length > 100) { |
| | | optimizationHistory.value = optimizationHistory.value.slice(0, 100) |
| | | } |
| | | } |
| | | |
| | | // æ´æ°å¾è¡¨æ°æ® |
| | | const updateCharts = () => { |
| | | chartInstances.value.forEach(instance => { |
| | | if (instance && instance.setOption) { |
| | | // è¿éå¯ä»¥æ´æ°å¾è¡¨æ°æ® |
| | | // 为äºç®åï¼æä»¬åªæ¯éæ°åå§åå¾è¡¨ |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // æ¹æ³ |
| | | const refreshData = () => { |
| | | generateMockData() |
| | | ElMessage.success('æ°æ®å·æ°æå') |
| | | } |
| | | |
| | | const applyOptimization = async () => { |
| | | try { |
| | | await ElMessageBox.confirm('ç¡®å®è¦åºç¨å½åçä¼å设置åï¼', '确认æä½', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }) |
| | | |
| | | // åºç¨ä¼å设置 |
| | | pumpData.value.currentFrequency = pumpData.value.optimizedFrequency |
| | | ElMessage.success('ä¼å设置åºç¨æå') |
| | | refreshData() |
| | | } catch (error) { |
| | | if (error !== 'cancel') { |
| | | ElMessage.error('åºç¨ä¼å设置失败') |
| | | } |
| | | } |
| | | } |
| | | |
| | | const emergencyStop = async () => { |
| | | try { |
| | | await ElMessageBox.confirm('ç¡®å®è¦ç´§æ¥åæ¢æææ³¨æ°´æ³µåï¼', 'ç´§æ¥æä½', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'error' |
| | | }) |
| | | |
| | | // æ§è¡ç´§æ¥åæ¢é»è¾ |
| | | pumpData.value.currentFrequency = 0 |
| | | pumpData.value.optimizedFrequency = 0 |
| | | ElMessage.success('ç´§æ¥åæ¢æ§è¡æå') |
| | | } catch (error) { |
| | | if (error !== 'cancel') { |
| | | ElMessage.error('ç´§æ¥åæ¢æ§è¡å¤±è´¥') |
| | | } |
| | | } |
| | | } |
| | | |
| | | const showOptimizationHistory = () => { |
| | | historyDialogVisible.value = true |
| | | } |
| | | |
| | | const updateModel = (model) => { |
| | | ElMessage.info(`æ´æ°æ¨¡å: ${model.modelName}`) |
| | | } |
| | | |
| | | const deleteModel = async (model) => { |
| | | try { |
| | | await ElMessageBox.confirm(`ç¡®å®è¦å 餿¨¡å ${model.modelName} åï¼`, '确认å é¤', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }) |
| | | |
| | | const index = modelConfigs.value.findIndex(m => m.modelName === model.modelName) |
| | | if (index > -1) { |
| | | modelConfigs.value.splice(index, 1) |
| | | ElMessage.success('模åå 餿å') |
| | | } |
| | | } catch (error) { |
| | | if (error !== 'cancel') { |
| | | ElMessage.error('模åå é¤å¤±è´¥') |
| | | } |
| | | } |
| | | } |
| | | |
| | | const toggleAutoRefresh = () => { |
| | | autoRefreshStatus.value = !autoRefreshStatus.value |
| | | if (autoRefreshStatus.value) { |
| | | startAutoRefresh() |
| | | ElMessage.success('èªå¨å·æ°å·²å¼å¯') |
| | | } else { |
| | | stopAutoRefresh() |
| | | ElMessage.info('èªå¨å·æ°å·²å
³é') |
| | | } |
| | | } |
| | | |
| | | const startAutoRefresh = () => { |
| | | stopAutoRefresh() // å
忢ä¹åç宿¶å¨ |
| | | autoRefreshTimer.value = setInterval(() => { |
| | | generateMockData() |
| | | }, 60000) // 1åé = 60000æ¯«ç§ |
| | | } |
| | | |
| | | const stopAutoRefresh = () => { |
| | | if (autoRefreshTimer.value) { |
| | | clearInterval(autoRefreshTimer.value) |
| | | autoRefreshTimer.value = null |
| | | } |
| | | } |
| | | |
| | | const formatTime = (timestamp) => { |
| | | return new Date(timestamp).toLocaleTimeString() |
| | | } |
| | | |
| | | // åå§åå¾è¡¨ |
| | | const initCharts = () => { |
| | | // é¢çä¼åè¶å¿å¾ |
| | | const frequencyChartInstance = echarts.init(frequencyChart.value) |
| | | const frequencyOption = { |
| | | title: { text: 'æ³µé¢çä¼åè¶å¿' }, |
| | | tooltip: { trigger: 'axis' }, |
| | | legend: { data: ['å½åé¢ç', 'ä¼åé¢ç', 'å°å±åå'] }, |
| | | xAxis: { type: 'category', data: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00'] }, |
| | | yAxis: [ |
| | | { type: 'value', name: 'é¢ç (Hz)' }, |
| | | { type: 'value', name: 'åå (MPa)' } |
| | | ], |
| | | series: [ |
| | | { |
| | | name: 'å½åé¢ç', |
| | | type: 'line', |
| | | data: [45.2, 44.8, 45.5, 45.1, 44.9, 45.2] |
| | | }, |
| | | { |
| | | name: 'ä¼åé¢ç', |
| | | type: 'line', |
| | | data: [42.1, 41.8, 42.3, 41.9, 41.7, 42.1] |
| | | }, |
| | | { |
| | | name: 'å°å±åå', |
| | | type: 'line', |
| | | yAxisIndex: 1, |
| | | data: [25.6, 25.8, 26.1, 25.9, 25.7, 25.6] |
| | | } |
| | | ] |
| | | } |
| | | frequencyChartInstance.setOption(frequencyOption) |
| | | chartInstances.value.push(frequencyChartInstance) |
| | | |
| | | // è½èåæå¾ |
| | | const energyChartInstance = echarts.init(energyChart.value) |
| | | const energyOption = { |
| | | title: { text: 'æ¥è½è对æ¯' }, |
| | | tooltip: { trigger: 'item' }, |
| | | legend: { orient: 'vertical', left: 'left',top: 'center' }, |
| | | series: [ |
| | | { |
| | | name: 'è½èåå¸', |
| | | type: 'pie', |
| | | radius: '50%', |
| | | data: [ |
| | | { value: 45, name: '注水泵' }, |
| | | { value: 25, name: 'ç
§æç³»ç»' }, |
| | | { value: 20, name: 'éé£ç³»ç»' }, |
| | | { value: 10, name: 'å
¶ä»è®¾å¤' } |
| | | ] |
| | | } |
| | | ] |
| | | } |
| | | energyChartInstance.setOption(energyOption) |
| | | chartInstances.value.push(energyChartInstance) |
| | | |
| | | // èè½ææå¾ |
| | | const savingChartInstance = echarts.init(savingChart.value) |
| | | const savingOption = { |
| | | title: { text: 'èè½ææè¶å¿' }, |
| | | tooltip: { trigger: 'axis' }, |
| | | xAxis: { type: 'category', data: ['å¨ä¸', 'å¨äº', 'å¨ä¸', 'å¨å', 'å¨äº', 'å¨å
', '卿¥'] }, |
| | | yAxis: { type: 'value', name: 'èè½ç (%)' }, |
| | | series: [ |
| | | { |
| | | name: 'èè½ç', |
| | | type: 'bar', |
| | | data: [12.5, 15.2, 18.7, 16.3, 19.1, 17.8, 15.8] |
| | | } |
| | | ] |
| | | } |
| | | savingChartInstance.setOption(savingOption) |
| | | chartInstances.value.push(savingChartInstance) |
| | | } |
| | | |
| | | // çæåå§å岿°æ® |
| | | const generateInitialHistory = () => { |
| | | for (let i = 0; i < 20; i++) { |
| | | const timestamp = new Date(Date.now() - i * 3600000).toLocaleString() |
| | | const record = { |
| | | timestamp, |
| | | formationPressure: parseFloat((20 + Math.random() * 10).toFixed(1)), |
| | | oldFrequency: parseFloat((40 + Math.random() * 10).toFixed(1)), |
| | | newFrequency: parseFloat((35 + Math.random() * 8).toFixed(1)), |
| | | energySaved: parseFloat((Math.random() * 5 + 1).toFixed(2)), |
| | | status: Math.random() > 0.1 ? 'success' : 'failed' |
| | | } |
| | | optimizationHistory.value.push(record) |
| | | } |
| | | } |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | initCharts() |
| | | generateInitialHistory() |
| | | refreshData() |
| | | if (autoRefreshStatus.value) { |
| | | startAutoRefresh() |
| | | } |
| | | }) |
| | | |
| | | onUnmounted(() => { |
| | | stopAutoRefresh() |
| | | chartInstances.value.forEach(instance => { |
| | | if (instance && instance.dispose) { |
| | | instance.dispose() |
| | | } |
| | | }) |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | |
| | | |
| | | .status-section { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .status-card { |
| | | height: 140px; |
| | | } |
| | | |
| | | .status-item { |
| | | display: flex; |
| | | align-items: center; |
| | | height: 100%; |
| | | } |
| | | |
| | | .status-icon { |
| | | font-size: 48px; |
| | | margin-right: 20px; |
| | | color: #409EFF; |
| | | } |
| | | |
| | | .status-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .status-title { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .status-value { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .status-detail { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .status-value.online, |
| | | .status-value.running { |
| | | color: #67C23A; |
| | | } |
| | | |
| | | .status-value.offline, |
| | | .status-value.stopped { |
| | | color: #F56C6C; |
| | | } |
| | | |
| | | .status-value.success { |
| | | color: #67C23A; |
| | | } |
| | | |
| | | .control-section, |
| | | .model-section, |
| | | .analysis-section { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .pump-control h4, |
| | | .pump-chart h4 { |
| | | margin-bottom: 20px; |
| | | color: #303133; |
| | | } |
| | | |
| | | .control-buttons { |
| | | margin-top: 20px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .control-buttons .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container product-view"> |
| | | <div class="left"> |
| | | <div> |
| | | <el-input |
| | | v-model="search" |
| | | style="width: 210px" |
| | | placeholder="è¾å
¥å
³é®åè¿è¡æç´¢" |
| | | @change="searchFilter" |
| | | @clear="searchFilter" |
| | | clearable |
| | | prefix-icon="Search" |
| | | /> |
| | | <el-button |
| | | type="primary" |
| | | @click="openProDia('addOne')" |
| | | style="margin-left: 10px" |
| | | >æ°å¢ç¶åºå</el-button |
| | | > |
| | | </div> |
| | | <div ref="containerRef"> |
| | | <el-tree |
| | | ref="tree" |
| | | v-loading="treeLoad" |
| | | :data="list" |
| | | @node-click="handleNodeClick" |
| | | :expand-on-click-node="false" |
| | | default-expand-all |
| | | :default-expanded-keys="expandedKeys" |
| | | :draggable="true" |
| | | :filter-node-method="filterNode" |
| | | :props="{ children: 'children', label: 'label' }" |
| | | highlight-current |
| | | node-key="id" |
| | | style=" |
| | | height: calc(100vh - 190px); |
| | | overflow-y: scroll; |
| | | scrollbar-width: none; |
| | | margin-top: 10px; |
| | | " |
| | | > |
| | | <template #default="{ node, data }"> |
| | | <div class="custom-tree-node"> |
| | | <span class="tree-node-content"> |
| | | <el-icon class="orange-icon"> |
| | | <component :is="data.children && data.children.length > 0 |
| | | ? node.expanded ? 'FolderOpened' : 'Folder' : 'Tickets'" /> |
| | | </el-icon> |
| | | {{ data.label }} |
| | | </span> |
| | | <div> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | @click="openProDia('edit', data)" |
| | | > |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button type="primary" link @click="openModelDia('add','', data.id)"> |
| | | æ·»å ååºå |
| | | </el-button> |
| | | <el-button |
| | | v-if="!node.childNodes.length" |
| | | style="margin-left: 4px" |
| | | type="danger" |
| | | link |
| | | @click="remove(node, data)" |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </el-tree> |
| | | </div> |
| | | </div> |
| | | <div class="right"> |
| | | <div style="margin-bottom: 10px" v-if="isShowButton"> |
| | | <el-button type="primary" @click="openModelDia('add')"> |
| | | æ°å¢ååºå |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | @click="handleDelete" |
| | | style="margin-left: 10px" |
| | | plain |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | ></PIMTable> |
| | | </div> |
| | | <el-dialog v-model="productDia" title="åºå" width="400px" @keydown.enter.prevent> |
| | | <el-form |
| | | :model="form" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="rules" |
| | | ref="formRef" |
| | | > |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="åºååç§°ï¼" prop="areaName"> |
| | | <el-input |
| | | v-model="form.areaName" |
| | | placeholder="请è¾å
¥äº§ååç§°" |
| | | clearable |
| | | @keydown.enter.prevent |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeProDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <el-dialog |
| | | v-model="modelDia" |
| | | title="ååºå" |
| | | width="400px" |
| | | @close="closeModelDia" |
| | | @keydown.enter.prevent |
| | | > |
| | | <el-form |
| | | :model="modelForm" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="modelRules" |
| | | ref="modelFormRef" |
| | | > |
| | | <el-form-item label="ç¶åºåï¼" prop="fuId"> |
| | | <el-cascader v-model="modelForm.fuId" :options="list" :props="{ |
| | | value: 'id', |
| | | label: 'label', |
| | | children: 'children', |
| | | checkStrictly: true, |
| | | }" /> |
| | | </el-form-item> |
| | | <el-form-item label="åºåç±»åï¼" prop="areaType"> |
| | | <el-select v-model="modelForm.areaType" placeholder="è¯·éæ©"> |
| | | <el-option v-for="item in area_type" :key="item.value" :label="item.label" :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="åºååç§°ï¼" prop="areaName"> |
| | | <el-input |
| | | v-model="modelForm.areaName" |
| | | placeholder="请è¾å
¥åä½" |
| | | clearable |
| | | @keydown.enter.prevent |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitModelForm">确认</el-button> |
| | | <el-button @click="closeModelDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { |
| | | areaAdd, |
| | | areaDelete, |
| | | areaListPage, |
| | | areaListTree, |
| | | } from "@/api/energyManagement/index.js"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const tree = ref(null); |
| | | const containerRef = ref(null); |
| | | |
| | | const productDia = ref(false); |
| | | const modelDia = ref(false); |
| | | const modelOperationType = ref(""); |
| | | const search = ref(""); |
| | | const currentId = ref(""); |
| | | const currentParentId = ref(""); |
| | | const operationType = ref(""); |
| | | const treeLoad = ref(false); |
| | | const list = ref([]); |
| | | const expandedKeys = ref([]); |
| | | const {area_type} = proxy.useDict("area_type") |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "åºååç§°", |
| | | prop: "areaName", |
| | | }, |
| | | { |
| | | label: "åºåç±»å", |
| | | prop: "areaType", |
| | | dataType: "tag", |
| | | formatData: (row) => { |
| | | return area_type.value.find(item => item.value == row)?.label; |
| | | } |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openModelDia("edit", row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const isShowButton = ref(false); |
| | | const selectedRows = ref([]); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | const data = reactive({ |
| | | form: { |
| | | areaName: "", |
| | | }, |
| | | rules: { |
| | | areaName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | }, |
| | | modelForm: { |
| | | areaName: "", |
| | | fuId: "", |
| | | }, |
| | | modelRules: { |
| | | areaName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | fuId: [{ required: true, message: "请è¾å
¥", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { form, rules, modelForm, modelRules } = toRefs(data); |
| | | |
| | | // æ¥è¯¢äº§åæ |
| | | const getProductTreeList = () => { |
| | | treeLoad.value = true; |
| | | areaListTree() |
| | | .then((res) => { |
| | | list.value = res; |
| | | list.value.forEach((a) => { |
| | | expandedKeys.value.push(a.label); |
| | | }); |
| | | treeLoad.value = false; |
| | | }) |
| | | .catch((err) => { |
| | | treeLoad.value = false; |
| | | }); |
| | | }; |
| | | // è¿æ»¤äº§åæ |
| | | const searchFilter = () => { |
| | | proxy.$refs.tree.filter(search.value); |
| | | }; |
| | | // æå¼äº§åå¼¹æ¡ |
| | | const openProDia = (type, data) => { |
| | | operationType.value = type; |
| | | productDia.value = true; |
| | | form.value.areaName = ""; |
| | | if (type === "edit") { |
| | | form.value.areaName = data.areaName; |
| | | } |
| | | }; |
| | | // æå¼è§æ ¼åå·å¼¹æ¡ |
| | | const openModelDia = (type, data,fatherId) => { |
| | | modelOperationType.value = type; |
| | | modelDia.value = true; |
| | | modelForm.value.fuId = ""; |
| | | modelForm.value.areaType = ""; |
| | | modelForm.value.areaName = ""; |
| | | modelForm.value.id = ""; |
| | | modelForm.value.fuId = fatherId; |
| | | if (type === "edit") { |
| | | modelForm.value = { ...data }; |
| | | } |
| | | }; |
| | | // æäº¤äº§ååç§°ä¿®æ¹ |
| | | const submitForm = () => { |
| | | proxy.$refs.formRef.validate((valid) => { |
| | | if (valid) { |
| | | if (operationType.value === "add") { |
| | | form.value.parentId = currentId.value; |
| | | form.value.id = ""; |
| | | } else if (operationType.value === "addOne") { |
| | | form.value.id = ""; |
| | | form.value.parentId = ""; |
| | | } else { |
| | | form.value.id = currentId.value; |
| | | form.value.parentId = ""; |
| | | } |
| | | areaAdd(form.value).then((res) => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeProDia(); |
| | | getProductTreeList(); |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | // å
³é产åå¼¹æ¡ |
| | | const closeProDia = () => { |
| | | proxy.$refs.formRef.resetFields(); |
| | | productDia.value = false; |
| | | }; |
| | | |
| | | // å é¤äº§å |
| | | const remove = (node, data) => { |
| | | let ids = []; |
| | | ids.push(data.id); |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | areaDelete(ids) |
| | | .then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getProductTreeList(); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // éæ©äº§å |
| | | const handleNodeClick = (val, node, el) => { |
| | | // 夿æ¯å¦ä¸ºå¶åèç¹ |
| | | isShowButton.value = !(val.children && val.children.length > 0); |
| | | // åªæå¶åèç¹ææ§è¡ä»¥ä¸é»è¾ |
| | | currentId.value = val.id; |
| | | currentParentId.value = val.parentId; |
| | | getModelList(true); |
| | | }; |
| | | |
| | | // æäº¤è§æ ¼åå·ä¿®æ¹ |
| | | const submitModelForm = () => { |
| | | proxy.$refs.modelFormRef.validate((valid) => { |
| | | if (valid) { |
| | | modelForm.value.fuId = currentId.value; |
| | | areaAdd(modelForm.value).then((res) => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeModelDia(); |
| | | getModelList(); |
| | | getProductTreeList(); |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | // å
³éåå·å¼¹æ¡ |
| | | const closeModelDia = () => { |
| | | proxy.$refs.modelFormRef.resetFields(); |
| | | modelDia.value = false; |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // æ¥è¯¢è§æ ¼åå· |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getModelList(); |
| | | }; |
| | | const getModelList = (val = false) => { |
| | | tableLoading.value = true; |
| | | let obj = { |
| | | id: currentId.value, |
| | | fuId:currentId.value, |
| | | current: page.current, |
| | | size: page.size |
| | | } |
| | | if(val){ |
| | | delete obj.id; |
| | | }else{ |
| | | delete obj.fuId |
| | | } |
| | | areaListPage(obj).then((res) => { |
| | | console.log("res", res); |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | // å é¤è§æ ¼åå· |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | areaDelete(ids) |
| | | .then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getModelList(); |
| | | getProductTreeList(); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // è°ç¨treeè¿æ»¤æ¹æ³ 䏿è±è¿æ»¤ |
| | | const filterNode = (value, data, node) => { |
| | | if (!value) { |
| | | //å¦ææ°æ®ä¸ºç©ºï¼åè¿åtrue,æ¾ç¤ºææçæ°æ®é¡¹ |
| | | return true; |
| | | } |
| | | // æ¥è¯¢å表æ¯å¦æå¹é
æ°æ®ï¼å°å¼å°åï¼å¹é
è±ææ°æ® |
| | | let val = value.toLowerCase(); |
| | | return chooseNode(val, data, node); // è°ç¨è¿æ»¤äºå±æ¹æ³ |
| | | }; |
| | | // è¿æ»¤ç¶èç¹ / åèç¹ (妿è¾å
¥çåæ°æ¯ç¶èç¹ä¸è½å¹é
ï¼åè¿å该èç¹ä»¥åå
¶ä¸çææåèç¹ï¼å¦æåæ°æ¯åèç¹ï¼åè¿å该èç¹çç¶èç¹ãnameæ¯ä¸æå符ï¼enNameæ¯è±æå符. |
| | | const chooseNode = (value, data, node) => { |
| | | if (data.label.indexOf(value) !== -1) { |
| | | return true; |
| | | } |
| | | const level = node.level; |
| | | // å¦æä¼ å
¥çèç¹æ¬èº«å°±æ¯ä¸çº§èç¹å°±ä¸ç¨æ ¡éªäº |
| | | if (level === 1) { |
| | | return false; |
| | | } |
| | | // å
åå½åèç¹çç¶èç¹ |
| | | let parentData = node.parent; |
| | | // éåå½åèç¹çç¶èç¹ |
| | | let index = 0; |
| | | while (index < level - 1) { |
| | | // 妿å¹é
å°ç´æ¥è¿åï¼æ¤å¤name弿¯ä¸æå符ï¼enNameæ¯è±æå符ã夿å¹é
ä¸è±æè¿æ»¤ |
| | | if (parentData.data.label.indexOf(value) !== -1) { |
| | | return true; |
| | | } |
| | | // å¦åçè¯åå¾ä¸ä¸å±åå¹é
|
| | | parentData = parentData.parent; |
| | | index++; |
| | | } |
| | | // 没å¹é
å°è¿åfalse |
| | | return false; |
| | | }; |
| | | getProductTreeList(); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .product-view { |
| | | display: flex; |
| | | } |
| | | .left { |
| | | width: 380px; |
| | | padding: 16px; |
| | | background: #ffffff; |
| | | } |
| | | .right { |
| | | width: calc(100% - 380px); |
| | | padding: 16px; |
| | | margin-left: 20px; |
| | | background: #ffffff; |
| | | } |
| | | .custom-tree-node { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | font-size: 14px; |
| | | padding-right: 8px; |
| | | } |
| | | .tree-node-content { |
| | | display: flex; |
| | | align-items: center; /* åç´å±
ä¸ */ |
| | | height: 100%; |
| | | } |
| | | .orange-icon { |
| | | color: orange; |
| | | font-size: 18px; |
| | | margin-right: 8px; /* 徿 䏿åä¹é´å ç¹é´è· */ |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>è½æºé©¾é©¶è±</h2> |
| | | <div class="header-info"> |
| | | <span class="update-time">æåæ´æ°ï¼{{ lastUpdateTime }}</span> |
| | | <el-button type="primary" size="small" @click="refreshData"> |
| | | <el-icon><Refresh /></el-icon> |
| | | å·æ°æ°æ® |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 宿¶è½èçæ§ --> |
| | | <div class="real-time-monitor"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <el-card class="monitor-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>çµåæ¶è</span> |
| | | <el-tag type="success" size="small">宿¶</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="monitor-content"> |
| | | <div class="monitor-value"> |
| | | <span class="value">{{ electricityConsumption }}</span> |
| | | <span class="unit">kW·h</span> |
| | | </div> |
| | | <div class="monitor-trend"> |
| | | <span class="trend-label">è¶å¿ï¼</span> |
| | | <el-tag :type="getTrendType(electricityTrend)" size="small"> |
| | | {{ electricityTrend > 0 ? 'â' : 'â' }} {{ Math.abs(electricityTrend) }}% |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-card class="monitor-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æ°´æ¶è</span> |
| | | <el-tag type="primary" size="small">宿¶</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="monitor-content"> |
| | | <div class="monitor-value"> |
| | | <span class="value">{{ waterConsumption }}</span> |
| | | <span class="unit">m³</span> |
| | | </div> |
| | | <div class="monitor-trend"> |
| | | <span class="trend-label">è¶å¿ï¼</span> |
| | | <el-tag :type="getTrendType(waterTrend)" size="small"> |
| | | {{ waterTrend > 0 ? 'â' : 'â' }} {{ Math.abs(waterTrend) }}% |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-card class="monitor-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æ°ä½æ¶è</span> |
| | | <el-tag type="warning" size="small">宿¶</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="monitor-content"> |
| | | <div class="monitor-value"> |
| | | <span class="value">{{ gasConsumption }}</span> |
| | | <span class="unit">m³</span> |
| | | </div> |
| | | <div class="monitor-trend"> |
| | | <span class="trend-label">è¶å¿ï¼</span> |
| | | <el-tag :type="getTrendType(gasTrend)" size="small"> |
| | | {{ gasTrend > 0 ? 'â' : 'â' }} {{ Math.abs(gasTrend) }}% |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- è½èè¶å¿åæ --> |
| | | <div class="trend-analysis"> |
| | | <el-card> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>è½èè¶å¿åæ</span> |
| | | <div class="time-selector"> |
| | | <el-radio-group v-model="trendTimeUnit" @change="handleTrendTimeChange"> |
| | | <el-radio value="hour">å°æ¶</el-radio> |
| | | <el-radio value="day">æ¥</el-radio> |
| | | <el-radio value="week">å¨</el-radio> |
| | | <el-radio value="month">æ</el-radio> |
| | | <el-radio value="year">å¹´</el-radio> |
| | | </el-radio-group> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <div class="chart-container"> |
| | | <div ref="trendChart" style="width: 100%; height: 400px;"></div> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- è½èç»è®¡ä¸æå --> |
| | | <div class="statistics-ranking"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-card class="statistics-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span class="card-title">è½èç»è®¡æ¥è¡¨</span> |
| | | <div class="header-actions"> |
| | | <el-select v-model="statisticsPeriod" @change="handleStatisticsChange" size="small" style="width: 100px;"> |
| | | <el-option label="æ¥ç»è®¡" value="day" /> |
| | | <el-option label="å¨ç»è®¡" value="week" /> |
| | | <el-option label="æç»è®¡" value="month" /> |
| | | <el-option label="å¹´ç»è®¡" value="year" /> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <div class="statistics-content"> |
| | | <div class="statistics-item"> |
| | | <span class="label">æ»è½èï¼</span> |
| | | <span class="value">{{ totalEnergyConsumption }} kW·h</span> |
| | | </div> |
| | | <div class="statistics-item"> |
| | | <span class="label">忝ï¼</span> |
| | | <span class="value" :class="getComparisonClass(yearOverYear)"> |
| | | {{ yearOverYear > 0 ? '+' : '' }}{{ yearOverYear }}% |
| | | </span> |
| | | </div> |
| | | <div class="statistics-item"> |
| | | <span class="label">ç¯æ¯ï¼</span> |
| | | <span class="value" :class="getComparisonClass(monthOverMonth)"> |
| | | {{ monthOverMonth > 0 ? '+' : '' }}{{ monthOverMonth }}% |
| | | </span> |
| | | </div> |
| | | <div class="statistics-item"> |
| | | <span class="label">èè½çï¼</span> |
| | | <span class="value success">{{ energySavingRate }}%</span> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-card class="ranking-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span class="card-title">è½èæå</span> |
| | | <el-select v-model="rankingType" @change="handleRankingChange" size="small" style="width: 120px;"> |
| | | <el-option label="é¨é¨æå" value="department" /> |
| | | <el-option label="è½¦é´æå" value="workshop" /> |
| | | <el-option label="è®¾å¤æå" value="equipment" /> |
| | | </el-select> |
| | | </div> |
| | | </template> |
| | | <div class="ranking-list"> |
| | | <div v-for="(item, index) in rankingList" :key="index" class="ranking-item"> |
| | | <div class="ranking-number" :class="getRankingClass(index + 1)">{{ index + 1 }}</div> |
| | | <div class="ranking-info"> |
| | | <div class="ranking-name">{{ item.name }}</div> |
| | | <div class="ranking-value">{{ item.value }} kW·h</div> |
| | | </div> |
| | | <div class="ranking-trend"> |
| | | <el-tag :type="getTrendType(item.trend)" size="small"> |
| | | {{ item.trend > 0 ? 'â' : 'â' }} {{ Math.abs(item.trend) }}% |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- å¼å¸¸åæä¸æºè½æ§å¶ --> |
| | | <div class="analysis-control"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-card class="abnormal-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span class="card-title">å¼å¸¸åæ</span> |
| | | <el-tag type="danger" size="small">{{ abnormalCount }}个å¼å¸¸</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="abnormal-list"> |
| | | <div v-for="(item, index) in abnormalList" :key="index" class="abnormal-item"> |
| | | <div class="abnormal-icon"> |
| | | <el-icon :color="getAbnormalColor(item.level)"> |
| | | <Warning v-if="item.level === 'warning'" /> |
| | | <CircleClose v-else /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="abnormal-content"> |
| | | <div class="abnormal-title">{{ item.title }}</div> |
| | | <div class="abnormal-desc">{{ item.description }}</div> |
| | | <div class="abnormal-time">{{ item.time }}</div> |
| | | </div> |
| | | <div class="abnormal-action"> |
| | | <el-button link size="small" @click="handleAbnormal(item)">å¤ç</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-card class="control-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span class="card-title">æºè½æ§å¶ç³»ç»</span> |
| | | <el-switch v-model="autoControlEnabled" @change="handleAutoControlChange" /> |
| | | </div> |
| | | </template> |
| | | <div class="control-content"> |
| | | <div class="control-item"> |
| | | <span class="label">å³°è°·å¹³çµä»·ç®¡çï¼</span> |
| | | <el-tag :type="getPriceType(currentPriceType)" size="small"> |
| | | {{ getPriceTypeText(currentPriceType) }} |
| | | </el-tag> |
| | | </div> |
| | | <div class="control-item"> |
| | | <span class="label">è´è·é¢æµï¼</span> |
| | | <span class="value">{{ loadForecast }} kW</span> |
| | | </div> |
| | | <div class="control-item"> |
| | | <span class="label">èªå¨å¯åï¼</span> |
| | | <el-tag :type="autoStartStop ? 'success' : 'info'" size="small"> |
| | | {{ autoStartStop ? 'å·²å¯ç¨' : 'å·²ç¦ç¨' }} |
| | | </el-tag> |
| | | </div> |
| | | <div class="control-item"> |
| | | <span class="label">æºè½è°èï¼</span> |
| | | <el-progress :percentage="intelligentAdjustment" :color="getProgressColor" /> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- ç¯ä¿ææ --> |
| | | <div class="environmental-indicators"> |
| | | <el-card> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>ç¯ä¿ææ çæ§</span> |
| | | </div> |
| | | </template> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <div class="indicator-item"> |
| | | <div class="indicator-title">ç¢³ææ¾é</div> |
| | | <div class="indicator-value">{{ carbonEmission }} kg</div> |
| | | <div class="indicator-trend"> |
| | | <span>忝ï¼</span> |
| | | <span :class="getComparisonClass(carbonEmissionTrend)"> |
| | | {{ carbonEmissionTrend > 0 ? '+' : '' }}{{ carbonEmissionTrend }}% |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <div class="indicator-item"> |
| | | <div class="indicator-title">ç¯ä¿è¾¾æ ç</div> |
| | | <div class="indicator-value">{{ environmentalCompliance }}%</div> |
| | | <div class="indicator-trend"> |
| | | <span>ç®æ ï¼</span> |
| | | <span class="success">95%</span> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <div class="indicator-item"> |
| | | <div class="indicator-title">绿è²è½æºå æ¯</div> |
| | | <div class="indicator-value">{{ greenEnergyRatio }}%</div> |
| | | <div class="indicator-trend"> |
| | | <span>ç®æ ï¼</span> |
| | | <span class="success">30%</span> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- å¤ç»´åº¦æ¥è¡¨ --> |
| | | <div class="multi-dimensional-reports"> |
| | | <el-card> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>å¤ç»´åº¦æ¥è¡¨</span> |
| | | </div> |
| | | </template> |
| | | <div class="report-filters"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="æ¶é´ç»´åº¦"> |
| | | <el-select v-model="reportTimeDimension" placeholder="éæ©æ¶é´ç»´åº¦"> |
| | | <el-option label="å°æ¶" value="hour" /> |
| | | <el-option label="æ¥" value="day" /> |
| | | <el-option label="å¨" value="week" /> |
| | | <el-option label="æ" value="month" /> |
| | | <el-option label="å¹´" value="year" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="é¨é¨ç»´åº¦"> |
| | | <el-select v-model="reportDepartmentDimension" placeholder="éæ©é¨é¨"> |
| | | <el-option label="å
¨é¨é¨é¨" value="all" /> |
| | | <el-option label="ç产é¨" value="production" /> |
| | | <el-option label="ææ¯é¨" value="technology" /> |
| | | <el-option label="è¡æ¿é¨" value="administration" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="设å¤ç»´åº¦"> |
| | | <el-select v-model="reportEquipmentDimension" placeholder="éæ©è®¾å¤ç±»å"> |
| | | <el-option label="å
¨é¨è®¾å¤" value="all" /> |
| | | <el-option label="çµå设å¤" value="electricity" /> |
| | | <el-option label="æ°´å¤ç设å¤" value="water" /> |
| | | <el-option label="æ°ä½è®¾å¤" value="gas" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="generateReport">çææ¥è¡¨</el-button> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | <div class="report-preview"> |
| | | <div class="report-data"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <div class="data-card"> |
| | | <div class="data-title">çµåæ¶è</div> |
| | | <div class="data-value">{{ reportData.electricity }} kW·h</div> |
| | | <div class="data-trend"> |
| | | <span :class="getTrendClass(reportData.electricityTrend)"> |
| | | {{ reportData.electricityTrend > 0 ? 'â' : 'â' }} {{ Math.abs(reportData.electricityTrend) }}% |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <div class="data-card"> |
| | | <div class="data-title">æ°´æ¶è</div> |
| | | <div class="data-value">{{ reportData.water }} m³</div> |
| | | <div class="data-trend"> |
| | | <span :class="getTrendClass(reportData.waterTrend)"> |
| | | {{ reportData.waterTrend > 0 ? 'â' : 'â' }} {{ Math.abs(reportData.waterTrend) }}% |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <div class="data-card"> |
| | | <div class="data-title">æ°ä½æ¶è</div> |
| | | <div class="data-value">{{ reportData.gas }} m³</div> |
| | | <div class="data-trend"> |
| | | <span :class="getTrendClass(reportData.gasTrend)"> |
| | | {{ reportData.gasTrend > 0 ? 'â' : 'â' }} {{ Math.abs(reportData.gasTrend) }}% |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <div class="report-chart"> |
| | | <div class="chart-title">è½èè¶å¿å¾</div> |
| | | <div class="chart-bars"> |
| | | <div v-for="(item, index) in reportData.chartData" :key="index" class="chart-bar"> |
| | | <div class="bar-label">{{ item.label }}</div> |
| | | <div class="bar-container"> |
| | | <div class="bar-fill" :style="{ height: item.percentage + '%', backgroundColor: item.color }"></div> |
| | | </div> |
| | | <div class="bar-value">{{ item.value }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="report-summary"> |
| | | <div class="summary-item"> |
| | | <span class="summary-label">æ»è½èï¼</span> |
| | | <span class="summary-value">{{ reportData.totalEnergy }} kW·h</span> |
| | | </div> |
| | | <div class="summary-item"> |
| | | <span class="summary-label">å¹³åè½èï¼</span> |
| | | <span class="summary-value">{{ reportData.averageEnergy }} kW·h</span> |
| | | </div> |
| | | <div class="summary-item"> |
| | | <span class="summary-label">è½èæçï¼</span> |
| | | <span class="summary-value">{{ reportData.efficiency }}%</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import * as echarts from 'echarts' |
| | | import { |
| | | Refresh, |
| | | Download, |
| | | Warning, |
| | | CircleClose, |
| | | Document, |
| | | Edit, |
| | | Bell |
| | | } from '@element-plus/icons-vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const lastUpdateTime = ref('') |
| | | const electricityConsumption = ref(0) |
| | | const waterConsumption = ref(0) |
| | | const gasConsumption = ref(0) |
| | | const electricityTrend = ref(0) |
| | | const waterTrend = ref(0) |
| | | const gasTrend = ref(0) |
| | | |
| | | // è¶å¿åæ |
| | | const trendTimeUnit = ref('day') |
| | | const trendChart = ref(null) |
| | | let chartInstance = null |
| | | |
| | | // ç»è®¡æ¥è¡¨ |
| | | const statisticsPeriod = ref('month') |
| | | const totalEnergyConsumption = ref(0) |
| | | const yearOverYear = ref(0) |
| | | const monthOverMonth = ref(0) |
| | | const energySavingRate = ref(0) |
| | | |
| | | // è½èæå |
| | | const rankingType = ref('department') |
| | | const rankingList = ref([]) |
| | | |
| | | // å¼å¸¸åæ |
| | | const abnormalCount = ref(0) |
| | | const abnormalList = ref([]) |
| | | |
| | | // æºè½æ§å¶ |
| | | const autoControlEnabled = ref(true) |
| | | const currentPriceType = ref('peak') |
| | | const loadForecast = ref(0) |
| | | const autoStartStop = ref(true) |
| | | const intelligentAdjustment = ref(0) |
| | | |
| | | // ç¯ä¿ææ |
| | | const carbonEmission = ref(0) |
| | | const carbonEmissionTrend = ref(0) |
| | | const environmentalCompliance = ref(0) |
| | | const greenEnergyRatio = ref(0) |
| | | |
| | | // å¤ç»´åº¦æ¥è¡¨ |
| | | const reportTimeDimension = ref('month') |
| | | const reportDepartmentDimension = ref('all') |
| | | const reportEquipmentDimension = ref('all') |
| | | const reportData = ref({ |
| | | electricity: 0, |
| | | water: 0, |
| | | gas: 0, |
| | | electricityTrend: 0, |
| | | waterTrend: 0, |
| | | gasTrend: 0, |
| | | totalEnergy: 0, |
| | | averageEnergy: 0, |
| | | efficiency: 0, |
| | | chartData: [] |
| | | }) |
| | | |
| | | // 宿¶å¨ |
| | | let updateTimer = null |
| | | |
| | | // è·åè¶å¿ç±»åæ ·å¼ |
| | | const getTrendType = (trend) => { |
| | | if (trend > 0) return 'danger' |
| | | if (trend < 0) return 'success' |
| | | return 'info' |
| | | } |
| | | |
| | | // è·å对æ¯ç±»åæ ·å¼ |
| | | const getComparisonClass = (value) => { |
| | | if (value > 0) return 'danger' |
| | | if (value < 0) return 'success' |
| | | return 'info' |
| | | } |
| | | |
| | | // è·åæåæ ·å¼ |
| | | const getRankingClass = (rank) => { |
| | | if (rank === 1) return 'ranking-first' |
| | | if (rank === 2) return 'ranking-second' |
| | | if (rank === 3) return 'ranking-third' |
| | | return 'ranking-normal' |
| | | } |
| | | |
| | | // è·åå¼å¸¸é¢è² |
| | | const getAbnormalColor = (level) => { |
| | | return level === 'warning' ? '#E6A23C' : '#F56C6C' |
| | | } |
| | | |
| | | // è·åçµä»·ç±»åæ ·å¼ |
| | | const getPriceType = (type) => { |
| | | const typeMap = { |
| | | peak: 'danger', |
| | | normal: 'warning', |
| | | valley: 'success' |
| | | } |
| | | return typeMap[type] || 'info' |
| | | } |
| | | |
| | | // è·åçµä»·ç±»åææ¬ |
| | | const getPriceTypeText = (type) => { |
| | | const typeMap = { |
| | | peak: 'å³°æ¶', |
| | | normal: 'å¹³æ¶', |
| | | valley: 'è°·æ¶' |
| | | } |
| | | return typeMap[type] || 'æªç¥' |
| | | } |
| | | |
| | | // è·åè¿åº¦æ¡é¢è² |
| | | const getProgressColor = (percentage) => { |
| | | if (percentage < 50) return '#67C23A' |
| | | if (percentage < 80) return '#E6A23C' |
| | | return '#F56C6C' |
| | | } |
| | | |
| | | // è·åè¶å¿æ ·å¼ |
| | | const getTrendClass = (trend) => { |
| | | if (trend > 0) return 'trend-up' |
| | | if (trend < 0) return 'trend-down' |
| | | return 'trend-stable' |
| | | } |
| | | |
| | | // æ¨¡ææ°æ®çæ |
| | | const generateMockData = () => { |
| | | // 宿¶è½èæ°æ® |
| | | electricityConsumption.value = Math.floor(Math.random() * 1000) + 2000 |
| | | waterConsumption.value = Math.floor(Math.random() * 100) + 150 |
| | | gasConsumption.value = Math.floor(Math.random() * 50) + 80 |
| | | |
| | | // è¶å¿æ°æ® |
| | | electricityTrend.value = (Math.random() * 20 - 10).toFixed(1) |
| | | waterTrend.value = (Math.random() * 15 - 7.5).toFixed(1) |
| | | gasTrend.value = (Math.random() * 12 - 6).toFixed(1) |
| | | |
| | | // ç»è®¡æ°æ® |
| | | totalEnergyConsumption.value = Math.floor(Math.random() * 50000) + 100000 |
| | | yearOverYear.value = (Math.random() * 20 - 10).toFixed(1) |
| | | monthOverMonth.value = (Math.random() * 15 - 7.5).toFixed(1) |
| | | energySavingRate.value = (Math.random() * 10 + 5).toFixed(1) |
| | | |
| | | // æåæ°æ® |
| | | rankingList.value = [ |
| | | { name: 'ç产车é´A', value: Math.floor(Math.random() * 5000) + 10000, trend: (Math.random() * 20 - 10).toFixed(1) }, |
| | | { name: 'ç产车é´B', value: Math.floor(Math.random() * 4000) + 8000, trend: (Math.random() * 20 - 10).toFixed(1) }, |
| | | { name: 'ææ¯ç åé¨', value: Math.floor(Math.random() * 3000) + 6000, trend: (Math.random() * 20 - 10).toFixed(1) }, |
| | | { name: 'è¡æ¿åå
¬åº', value: Math.floor(Math.random() * 2000) + 4000, trend: (Math.random() * 20 - 10).toFixed(1) }, |
| | | { name: 'åå¤ä¿éåº', value: Math.floor(Math.random() * 1500) + 3000, trend: (Math.random() * 20 - 10).toFixed(1) } |
| | | ].sort((a, b) => b.value - a.value) |
| | | |
| | | // å¼å¸¸æ°æ® |
| | | abnormalCount.value = Math.floor(Math.random() * 5) + 1 |
| | | abnormalList.value = [ |
| | | { level: 'warning', title: 'çµåè´è·è¿é«', description: 'ç产车é´Açµåè´è·è¾¾å°85%ï¼å»ºè®®æ£æ¥è®¾å¤è¿è¡ç¶æ', time: '2åéå' }, |
| | | { level: 'error', title: 'æ°´åå¼å¸¸', description: 'æ°´å¤ç设å¤ååå¼å¸¸ï¼å½ååå0.3MPaï¼ä½äºæ£å¸¸èå´', time: '5åéå' } |
| | | ] |
| | | |
| | | // æºè½æ§å¶æ°æ® |
| | | loadForecast.value = Math.floor(Math.random() * 500) + 1500 |
| | | intelligentAdjustment.value = Math.floor(Math.random() * 30) + 60 |
| | | |
| | | // ç¯ä¿ææ |
| | | carbonEmission.value = Math.floor(Math.random() * 1000) + 5000 |
| | | carbonEmissionTrend.value = (Math.random() * 15 - 7.5).toFixed(1) |
| | | environmentalCompliance.value = (Math.random() * 5 + 95).toFixed(1) |
| | | greenEnergyRatio.value = (Math.random() * 10 + 25).toFixed(1) |
| | | |
| | | // æ´æ°æåæ´æ°æ¶é´ |
| | | lastUpdateTime.value = new Date().toLocaleString() |
| | | |
| | | // åæ¶æ´æ°æ¥è¡¨æ°æ® |
| | | generateReportData() |
| | | } |
| | | |
| | | // åå§åè¶å¿å¾è¡¨ |
| | | const initTrendChart = () => { |
| | | if (chartInstance) { |
| | | chartInstance.dispose() |
| | | } |
| | | |
| | | chartInstance = echarts.init(trendChart.value) |
| | | |
| | | const option = { |
| | | title: { |
| | | text: 'è½èè¶å¿åæ', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis' |
| | | }, |
| | | legend: { |
| | | data: ['çµå', 'æ°´', 'æ°ä½'], |
| | | bottom: 10 |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: generateTimeData() |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'æ¶èé' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'çµå', |
| | | type: 'line', |
| | | data: generateSeriesData(), |
| | | smooth: true |
| | | }, |
| | | { |
| | | name: 'æ°´', |
| | | type: 'line', |
| | | data: generateSeriesData(), |
| | | smooth: true |
| | | }, |
| | | { |
| | | name: 'æ°ä½', |
| | | type: 'line', |
| | | data: generateSeriesData(), |
| | | smooth: true |
| | | } |
| | | ] |
| | | } |
| | | |
| | | chartInstance.setOption(option) |
| | | } |
| | | |
| | | // çææ¶é´æ°æ® |
| | | const generateTimeData = () => { |
| | | const data = [] |
| | | const now = new Date() |
| | | |
| | | switch (trendTimeUnit.value) { |
| | | case 'hour': |
| | | for (let i = 23; i >= 0; i--) { |
| | | const time = new Date(now.getTime() - i * 60 * 60 * 1000) |
| | | data.unshift(time.getHours() + ':00') |
| | | } |
| | | break |
| | | case 'day': |
| | | for (let i = 29; i >= 0; i--) { |
| | | const time = new Date(now.getTime() - i * 24 * 60 * 60 * 1000) |
| | | data.unshift(time.getDate() + 'æ¥') |
| | | } |
| | | break |
| | | case 'week': |
| | | for (let i = 11; i >= 0; i--) { |
| | | data.unshift(`第${12 - i}å¨`) |
| | | } |
| | | break |
| | | case 'month': |
| | | for (let i = 11; i >= 0; i--) { |
| | | const month = (12 - i) % 12 || 12 |
| | | data.unshift(`${month}æ`) |
| | | } |
| | | break |
| | | case 'year': |
| | | for (let i = 4; i >= 0; i--) { |
| | | const year = new Date().getFullYear() - i |
| | | data.unshift(`${year}å¹´`) |
| | | } |
| | | break |
| | | } |
| | | |
| | | return data |
| | | } |
| | | |
| | | // çæç³»åæ°æ® |
| | | const generateSeriesData = () => { |
| | | const data = [] |
| | | const count = trendTimeUnit.value === 'hour' ? 24 : |
| | | trendTimeUnit.value === 'day' ? 30 : |
| | | trendTimeUnit.value === 'week' ? 12 : |
| | | trendTimeUnit.value === 'month' ? 12 : 5 |
| | | |
| | | for (let i = 0; i < count; i++) { |
| | | data.push(Math.floor(Math.random() * 1000) + 500) |
| | | } |
| | | |
| | | return data |
| | | } |
| | | |
| | | // å¤çè¶å¿æ¶é´åå |
| | | const handleTrendTimeChange = () => { |
| | | nextTick(() => { |
| | | initTrendChart() |
| | | }) |
| | | } |
| | | |
| | | // å¤çç»è®¡å¨æåå |
| | | const handleStatisticsChange = () => { |
| | | generateMockData() |
| | | } |
| | | |
| | | // å¤çæåç±»ååå |
| | | const handleRankingChange = () => { |
| | | // æ ¹æ®ç±»åéæ°çææåæ°æ® |
| | | generateMockData() |
| | | } |
| | | |
| | | // å¤çèªå¨æ§å¶åå |
| | | const handleAutoControlChange = (value) => { |
| | | ElMessage.success(`æºè½æ§å¶ç³»ç»å·²${value ? 'å¯ç¨' : 'ç¦ç¨'}`) |
| | | } |
| | | |
| | | // å¤çå¼å¸¸ |
| | | const handleAbnormal = (item) => { |
| | | ElMessage.info(`æ£å¨å¤çå¼å¸¸ï¼${item.title}`) |
| | | } |
| | | |
| | | // å·æ°æ°æ® |
| | | const refreshData = () => { |
| | | generateMockData() |
| | | if (chartInstance) { |
| | | initTrendChart() |
| | | } |
| | | ElMessage.success('æ°æ®å·²å·æ°') |
| | | } |
| | | |
| | | // 导åºç»è®¡ |
| | | const exportStatistics = () => { |
| | | ElMessage.success('ç»è®¡æ°æ®å¯¼åºæå') |
| | | } |
| | | |
| | | // 导åºç¯ä¿æ¥å |
| | | const exportEnvironmentalReport = () => { |
| | | ElMessage.success('ç¯ä¿æ¥åå¯¼åºæå') |
| | | } |
| | | |
| | | // çæèªå®ä¹æ¥è¡¨ |
| | | const generateCustomReport = () => { |
| | | ElMessage.info('èªå®ä¹æ¥è¡¨åè½å¼åä¸...') |
| | | } |
| | | |
| | | // 订é
æ¥è¡¨ |
| | | const subscribeReport = () => { |
| | | ElMessage.info('æ¥è¡¨è®¢é
åè½å¼åä¸...') |
| | | } |
| | | |
| | | // çææ¥è¡¨æ°æ® |
| | | const generateReportData = () => { |
| | | // çæåºç¡æ°æ® |
| | | reportData.value.electricity = Math.floor(Math.random() * 5000) + 8000 |
| | | reportData.value.water = Math.floor(Math.random() * 200) + 300 |
| | | reportData.value.gas = Math.floor(Math.random() * 100) + 150 |
| | | |
| | | // çæè¶å¿æ°æ® |
| | | reportData.value.electricityTrend = (Math.random() * 20 - 10).toFixed(1) |
| | | reportData.value.waterTrend = (Math.random() * 15 - 7.5).toFixed(1) |
| | | reportData.value.gasTrend = (Math.random() * 12 - 6).toFixed(1) |
| | | |
| | | // è®¡ç®æ»è½èåå¹³åè½è |
| | | reportData.value.totalEnergy = reportData.value.electricity + reportData.value.water * 0.1 + reportData.value.gas * 0.05 |
| | | reportData.value.averageEnergy = Math.floor(reportData.value.totalEnergy / 3) |
| | | reportData.value.efficiency = (Math.random() * 20 + 80).toFixed(1) |
| | | |
| | | // çæå¾è¡¨æ°æ® |
| | | const labels = ['å¨ä¸', 'å¨äº', 'å¨ä¸', 'å¨å', 'å¨äº', 'å¨å
', '卿¥'] |
| | | const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399', '#9c27b0', '#ff9800'] |
| | | |
| | | reportData.value.chartData = labels.map((label, index) => ({ |
| | | label, |
| | | value: Math.floor(Math.random() * 1000) + 500, |
| | | percentage: Math.floor(Math.random() * 40) + 30, |
| | | color: colors[index] |
| | | })) |
| | | } |
| | | |
| | | // çææ¥è¡¨ |
| | | const generateReport = () => { |
| | | generateReportData() |
| | | ElMessage.success('æ¥è¡¨çææå') |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | // å¯å¨å®æ¶æ´æ° |
| | | const startAutoUpdate = () => { |
| | | updateTimer = setInterval(() => { |
| | | generateMockData() |
| | | if (chartInstance) { |
| | | initTrendChart() |
| | | } |
| | | }, 60000) // æ¯åéæ´æ°ä¸æ¬¡ |
| | | } |
| | | |
| | | // 忢宿¶æ´æ° |
| | | const stopAutoUpdate = () => { |
| | | if (updateTimer) { |
| | | clearInterval(updateTimer) |
| | | updateTimer = null |
| | | } |
| | | } |
| | | |
| | | // ç»ä»¶æè½½ |
| | | onMounted(() => { |
| | | generateMockData() |
| | | nextTick(() => { |
| | | initTrendChart() |
| | | }) |
| | | startAutoUpdate() |
| | | }) |
| | | |
| | | // ç»ä»¶å¸è½½ |
| | | onUnmounted(() => { |
| | | stopAutoUpdate() |
| | | if (chartInstance) { |
| | | chartInstance.dispose() |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .app-container { |
| | | padding: 12px; |
| | | background: #f5f5f5; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | padding: 16px; |
| | | background: white; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | |
| | | h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | font-size: 22px; |
| | | } |
| | | |
| | | .header-info { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | |
| | | .update-time { |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .real-time-monitor { |
| | | margin-bottom: 12px; |
| | | |
| | | .monitor-card { |
| | | .monitor-content { |
| | | text-align: center; |
| | | padding: 16px 0; |
| | | |
| | | .monitor-value { |
| | | margin-bottom: 12px; |
| | | |
| | | .value { |
| | | font-size: 28px; |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .unit { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | margin-left: 4px; |
| | | } |
| | | } |
| | | |
| | | .monitor-trend { |
| | | .trend-label { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-right: 6px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .trend-analysis { |
| | | margin-bottom: 12px; |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .time-selector { |
| | | .el-radio-group { |
| | | .el-radio { |
| | | margin-right: 4px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .chart-container { |
| | | padding: 16px 0; |
| | | } |
| | | } |
| | | |
| | | .statistics-ranking { |
| | | margin-bottom: 12px; |
| | | |
| | | .statistics-card, .ranking-card { |
| | | height: 100%; |
| | | box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); |
| | | border-radius: 8px; |
| | | transition: all 0.3s ease; |
| | | |
| | | &:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); |
| | | } |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 12px 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #fafafa; |
| | | |
| | | .card-title { |
| | | font-size: 15px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | gap: 8px; |
| | | align-items: center; |
| | | } |
| | | } |
| | | |
| | | .statistics-content { |
| | | padding: 16px; |
| | | |
| | | .statistics-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | padding: 10px 12px; |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | transition: background-color 0.3s ease; |
| | | |
| | | &:hover { |
| | | background: #e9ecef; |
| | | } |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .label { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .value { |
| | | font-weight: bold; |
| | | font-size: 15px; |
| | | |
| | | &.success { |
| | | color: #67c23a; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .ranking-list { |
| | | padding: 16px; |
| | | |
| | | .ranking-item { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 12px; |
| | | margin-bottom: 6px; |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | transition: all 0.3s ease; |
| | | |
| | | &:hover { |
| | | background: #e9ecef; |
| | | transform: translateX(4px); |
| | | } |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .ranking-number { |
| | | width: 32px; |
| | | height: 32px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-weight: bold; |
| | | font-size: 14px; |
| | | margin-right: 12px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | |
| | | &.ranking-first { |
| | | background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%); |
| | | color: #fff; |
| | | } |
| | | |
| | | &.ranking-second { |
| | | background: linear-gradient(135deg, #c0c0c0 0%, #d4d4d4 100%); |
| | | color: #fff; |
| | | } |
| | | |
| | | &.ranking-third { |
| | | background: linear-gradient(135deg, #cd7f32 0%, #daa520 100%); |
| | | color: #fff; |
| | | } |
| | | |
| | | &.ranking-normal { |
| | | background: linear-gradient(135deg, #f5f5f5 0%, #e9ecef 100%); |
| | | color: #909399; |
| | | } |
| | | } |
| | | |
| | | .ranking-info { |
| | | flex: 1; |
| | | |
| | | .ranking-name { |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin-bottom: 4px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .ranking-value { |
| | | color: #606266; |
| | | font-size: 13px; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | |
| | | .ranking-trend { |
| | | margin-left: 12px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .analysis-control { |
| | | margin-bottom: 20px; |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .abnormal-list { |
| | | .abnormal-item { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | padding: 15px 0; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | |
| | | &:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .abnormal-icon { |
| | | margin-right: 15px; |
| | | margin-top: 2px; |
| | | } |
| | | |
| | | .abnormal-content { |
| | | flex: 1; |
| | | |
| | | .abnormal-title { |
| | | font-weight: bold; |
| | | color: #303133; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .abnormal-desc { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .abnormal-time { |
| | | color: #909399; |
| | | font-size: 12px; |
| | | } |
| | | } |
| | | |
| | | .abnormal-action { |
| | | margin-left: 15px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .control-content { |
| | | .control-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .label { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .environmental-indicators { |
| | | margin-bottom: 20px; |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | } |
| | | |
| | | .indicator-item { |
| | | text-align: center; |
| | | padding: 20px 0; |
| | | |
| | | .indicator-title { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .indicator-value { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .indicator-trend { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | |
| | | .success { |
| | | color: #67c23a; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .multi-dimensional-reports { |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | } |
| | | |
| | | .report-filters { |
| | | padding: 20px 0; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .report-preview { |
| | | .report-data { |
| | | padding: 20px 0; |
| | | |
| | | .data-card { |
| | | text-align: center; |
| | | padding: 16px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | margin-bottom: 16px; |
| | | |
| | | .data-title { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .data-value { |
| | | font-size: 20px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .data-trend { |
| | | font-size: 12px; |
| | | |
| | | .trend-up { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .trend-down { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .trend-stable { |
| | | color: #909399; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .report-chart { |
| | | margin: 20px 0; |
| | | padding: 20px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | |
| | | .chart-title { |
| | | text-align: center; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .chart-bars { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: flex-end; |
| | | height: 120px; |
| | | |
| | | .chart-bar { |
| | | text-align: center; |
| | | flex: 1; |
| | | margin: 0 8px; |
| | | |
| | | .bar-label { |
| | | font-size: 12px; |
| | | color: #606266; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .bar-container { |
| | | height: 80px; |
| | | background: #e9ecef; |
| | | border-radius: 4px; |
| | | position: relative; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .bar-fill { |
| | | position: absolute; |
| | | bottom: 0; |
| | | left: 0; |
| | | right: 0; |
| | | border-radius: 4px; |
| | | transition: height 0.3s ease; |
| | | } |
| | | |
| | | .bar-value { |
| | | font-size: 12px; |
| | | color: #303133; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .report-summary { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | padding: 20px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | |
| | | .summary-item { |
| | | text-align: center; |
| | | |
| | | .summary-label { |
| | | display: block; |
| | | color: #606266; |
| | | font-size: 14px; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .summary-value { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // éç¨æ ·å¼ |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .success { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .danger { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .warning { |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | .info { |
| | | color: #909399; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">æ¥æï¼</span> |
| | | <!-- <el-time-picker |
| | | style="width: 240px;margin-right: 10px" |
| | | v-model="searchForm.startTime" |
| | | value-format="HH:mm:ss" |
| | | format="HH:mm:ss" |
| | | type="time" |
| | | placeholder="è¯·éæ©å¼å§æ¶é´" |
| | | clearable |
| | | /> --> |
| | | <el-date-picker |
| | | v-model="searchForm.date" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | :size="size" |
| | | /> |
| | | <!-- <el-time-picker |
| | | v-model="searchForm.timeRange" |
| | | is-range |
| | | arrow-control |
| | | range-separator="To" |
| | | start-placeholder="éæ©ç»ææ¶é´" |
| | | end-placeholder="éæ©ç»ææ¶é´" |
| | | /> --> |
| | | <span class="search_title">çµä»·ï¼å
/度ï¼ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.price" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥çµä»·" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | ></PIMTable> |
| | | </div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | title="ç¨çµæ¶æ®µç®¡ç" |
| | | width="70%" |
| | | @close="closeDia" |
| | | > |
| | | <el-form |
| | | :model="form" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="rules" |
| | | ref="formRef" |
| | | > |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¥æï¼" prop="date"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | | v-model="form.date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="çµä»·ï¼å
/度ï¼ï¼" prop="price"> |
| | | <el-input |
| | | v-model="form.price" |
| | | placeholder="请è¾å
¥çµä»·" |
| | | clearable |
| | | type="number" |
| | | step="0.01" |
| | | min="0" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="峰段ï¼" prop="peak"> |
| | | <el-input |
| | | v-model="form.peak" |
| | | placeholder="请è¾å
¥å³°æ®µ" |
| | | clearable |
| | | type="number" |
| | | step="0.01" |
| | | min="0" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="谷段ï¼" prop="valley"> |
| | | <el-input |
| | | v-model="form.valley" |
| | | placeholder="请è¾å
¥è°·æ®µ" |
| | | clearable |
| | | type="number" |
| | | step="0.01" |
| | | min="0" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="平段ï¼" prop="flat"> |
| | | <el-input |
| | | v-model="form.flat" |
| | | placeholder="请è¾å
¥å¹³æ®µ" |
| | | clearable |
| | | type="number" |
| | | step="0.01" |
| | | min="0" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å°æ®µï¼" prop="sharp"> |
| | | <el-input |
| | | v-model="form.sharp" |
| | | placeholder="请è¾å
¥å°æ®µ" |
| | | clearable |
| | | type="number" |
| | | step="0.01" |
| | | min="0" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | <script setup> |
| | | import {Search} from "@element-plus/icons-vue"; |
| | | import {onMounted, ref} from "vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import {getToken} from "@/utils/auth.js"; |
| | | import {periodListPage,periodDelete,periodAdd,periodUpdate} from "@/api/energyManagement/index.js"; |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | date: "", |
| | | price: "" |
| | | }, |
| | | form: { |
| | | date: "", |
| | | price: "", |
| | | peak: "", |
| | | valley: "", |
| | | flat: "", |
| | | sharp: "" |
| | | } |
| | | }); |
| | | const { searchForm,form } = toRefs(data); |
| | | const page = ref({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0 |
| | | }); |
| | | const dialogFormVisible = ref(false); |
| | | const selectedRows = ref([]); |
| | | const operationType = ref(''); |
| | | const tableData = ref([]); |
| | | const emit = defineEmits(['close']) |
| | | const tableLoading = ref(false); |
| | | const tableColumn = ref([ |
| | | // { |
| | | // label: "æ¶æ®µåç§°", |
| | | // prop: "timeName", |
| | | // width: 200, |
| | | // }, |
| | | { |
| | | label: "æ¥æ", |
| | | prop: "date", |
| | | width: 200, |
| | | }, |
| | | { |
| | | label: "çµä»·ï¼å
/度ï¼", |
| | | prop: "price", |
| | | width: 200, |
| | | }, |
| | | { |
| | | label: "峰段", |
| | | prop: "peak", |
| | | }, |
| | | { |
| | | label: "谷段", |
| | | prop: "valley", |
| | | }, |
| | | { |
| | | label: "平段", |
| | | prop: "flat", |
| | | }, |
| | | { |
| | | label: "å°æ®µ", |
| | | prop: "sharp", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | |
| | | |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | const formDia = ref() |
| | | const upload = reactive({ |
| | | // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼å®¢æ·å¯¼å
¥ï¼ |
| | | open: false, |
| | | // å¼¹åºå±æ é¢ï¼å®¢æ·å¯¼å
¥ï¼ |
| | | title: "", |
| | | // æ¯å¦ç¦ç¨ä¸ä¼ |
| | | isUploading: false, |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/equipmentEnergyConsumption/importData", |
| | | // æä»¶ä¸ä¼ åçåè° |
| | | beforeUpload: (file) => { |
| | | console.log('æä»¶å³å°ä¸ä¼ ', file); |
| | | // å¯ä»¥å¨æ¤å¤åæä»¶ç±»åæå¤§å°æ ¡éª |
| | | const isValid = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || file.name.endsWith('.xlsx') || file.name.endsWith('.xls'); |
| | | if (!isValid) { |
| | | proxy.$modal.msgError("åªè½ä¸ä¼ Excel æä»¶"); |
| | | } |
| | | return isValid; |
| | | }, |
| | | // æä»¶ç¶ææ¹åæ¶çåè° |
| | | onChange: (file, fileList) => { |
| | | console.log('æä»¶ç¶ææ¹å', file, fileList); |
| | | }, |
| | | // æä»¶ä¸ä¼ æåæ¶çåè° |
| | | onSuccess: (response, file, fileList) => { |
| | | console.log('ä¸ä¼ æå', response, file, fileList); |
| | | if(response.code === 200){ |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); |
| | | }else if(response.code === 500){ |
| | | ElMessageBox.error(response.msg); |
| | | }else{ |
| | | ElMessageBox.warning(response.msg); |
| | | } |
| | | }, |
| | | // æä»¶ä¸ä¼ 失败æ¶çåè° |
| | | onError: (error, file, fileList) => { |
| | | console.error('ä¸ä¼ 失败', error, file, fileList); |
| | | ElMessageBox.error("æä»¶ä¸ä¼ 失败"); |
| | | }, |
| | | // æä»¶ä¸ä¼ è¿åº¦åè° |
| | | onProgress: (event, file, fileList) => { |
| | | console.log('ä¸ä¼ ä¸...', event.percent); |
| | | } |
| | | }); |
| | | |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | //éç½® |
| | | const resetFilters = () => { |
| | | searchForm.value = { |
| | | date: "", |
| | | price: "" |
| | | }; |
| | | getList(); |
| | | |
| | | }; |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | periodListPage({ ...searchForm, ...page.value }).then((res) => { |
| | | tableLoading.value = false; |
| | | if (res && res.data) { |
| | | tableData.value = res.data.records || []; |
| | | page.total = res.data.total || 0; |
| | | } else { |
| | | tableData.value = []; |
| | | page.total = 0; |
| | | ElMessageBox.warning('æªè·åå°æ°æ®'); |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | tableLoading.value = false; |
| | | console.error('æ°æ®å 载失败:', err); |
| | | ElMessageBox.error('æ°æ®å 载失败ï¼è¯·éè¯'); |
| | | }); |
| | | }; |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | // form.value.maintainer = userStore.nickName; |
| | | // form.value.maintenanceTime = getCurrentDate(); |
| | | form.value = {} |
| | | proxy.resetForm("formRef"); |
| | | periodListPage().then((res) => { |
| | | codeList.value = res.data; |
| | | }); |
| | | if (type === "edit") { |
| | | form.value = {...row} |
| | | } |
| | | } |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | openDialog(type, row) |
| | | }; |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | if (operationType.value === "add") { |
| | | periodAdd(form.value).then(response => { |
| | | proxy.$modal.msgSuccess("æ°å¢æå") |
| | | closeDia() |
| | | getList() |
| | | }) |
| | | } else { |
| | | periodUpdate(form.value).then(response => { |
| | | proxy.$modal.msgSuccess("ä¿®æ¹æå") |
| | | closeDia() |
| | | getList() |
| | | }) |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | /** 导å
¥æé®æä½ */ |
| | | function handleImport() { |
| | | upload.title = "设å¤è½è"; |
| | | upload.open = true; |
| | | // æ¸
ç©ºä¸æ¬¡ä¸ä¼ çæä»¶å表 |
| | | nextTick(() => { |
| | | proxy.$refs["uploadRef"]?.clearFiles(); |
| | | }); |
| | | } |
| | | function importTemplate() { |
| | | proxy.download( |
| | | "/equipmentEnergyConsumption/export", |
| | | {}, |
| | | '设å¤è½è导å
¥æ¨¡ç.xlsx' |
| | | ); |
| | | } |
| | | /** æäº¤ä¸ä¼ æä»¶ */ |
| | | function submitFileForm() { |
| | | proxy.$refs["uploadRef"].submit(); |
| | | } |
| | | |
| | | /** å¼¹æ¡å
³éæ¶æ¸
空æä»¶å表 */ |
| | | function handleDialogClose() { |
| | | nextTick(() => { |
| | | proxy.$refs["uploadRef"]?.clearFiles(); |
| | | }); |
| | | } |
| | | |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | periodDelete(ids) |
| | | .then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¨çµæ¶èåºåï¼" prop="electricityConsumptionAreaId"> |
| | | <el-cascader |
| | | v-model="form.electricityConsumptionAreaId" |
| | | :options="areaList" |
| | | :props="{ |
| | | value: 'id', |
| | | label: 'label', |
| | | children: 'children', |
| | | checkStrictly: true, |
| | | }" |
| | | placeholder="è¯·éæ©åºå" |
| | | clearable |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯æ¥éå¶çµéï¼" prop="everyNum"> |
| | | <el-input |
| | | v-model="form.everyNum" |
| | |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¢å®åçï¼" prop="powerRating"> |
| | | <el-input |
| | |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å®é
åçï¼" prop="powerActual"> |
| | | <el-input |
| | |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¿è¡æ¶é´ï¼" prop="runDate"> |
| | | <el-date-picker |
| | |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="彿¥ç¨çµéï¼" prop="dayNum"> |
| | | <el-input |
| | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import {deviceList, equipmentEnergyAdd, equipmentEnergyUpdate} from "@/api/energyManagement/index.js"; |
| | | import {deviceList, equipmentEnergyAdd, equipmentEnergyUpdate, areaListTree} from "@/api/energyManagement/index.js"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | const dialogFormVisible = ref(false); |
| | |
| | | powerActual: "", |
| | | runDate: "", |
| | | dayNum: "", |
| | | electricityConsumptionAreaId: "", |
| | | }, |
| | | rules: { |
| | | code: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | |
| | | powerRating: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | powerActual: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | dayNum: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | electricityConsumptionAreaId: [{ required: true, message: "è¯·éæ©åºå", trigger: "change" }], |
| | | }, |
| | | }) |
| | | const { form, rules } = toRefs(data); |
| | | const codeList = ref([]) |
| | | const areaList = ref([]) |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | |
| | | // form.value.maintenanceTime = getCurrentDate(); |
| | | form.value = {} |
| | | proxy.resetForm("formRef"); |
| | | |
| | | // è·å设å¤å表 |
| | | deviceList().then((res) => { |
| | | codeList.value = res.data; |
| | | }); |
| | | |
| | | // è·ååºåå表 |
| | | areaListTree().then((res) => { |
| | | areaList.value = res; |
| | | console.log("areaList", res); |
| | | }); |
| | | |
| | | if (type === "edit") { |
| | | form.value = {...row} |
| | | // ç¼è¾æ¶ï¼å°å个ID转æ¢ä¸ºæ°ç»æ ¼å¼ç¨äºåæ¾ |
| | | if (row.electricityConsumptionAreaId) { |
| | | form.value.electricityConsumptionAreaId = [row.electricityConsumptionAreaId]; |
| | | } |
| | | } |
| | | } |
| | | const setName = (code) => { |
| | |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | // æäº¤åå¤ç electricityConsumptionAreaIdï¼åæ°ç»çæåä¸ä¸ªå¼ |
| | | const submitData = { ...form.value }; |
| | | if (Array.isArray(submitData.electricityConsumptionAreaId) && submitData.electricityConsumptionAreaId.length > 0) { |
| | | submitData.electricityConsumptionAreaId = submitData.electricityConsumptionAreaId[submitData.electricityConsumptionAreaId.length - 1]; |
| | | } |
| | | |
| | | if (operationType.value === "add") { |
| | | equipmentEnergyAdd(form.value).then(response => { |
| | | equipmentEnergyAdd(submitData).then(response => { |
| | | proxy.$modal.msgSuccess("æ°å¢æå") |
| | | closeDia() |
| | | }) |
| | | } else { |
| | | equipmentEnergyUpdate(form.value).then(response => { |
| | | equipmentEnergyUpdate(submitData).then(response => { |
| | | proxy.$modal.msgSuccess("ä¿®æ¹æå") |
| | | closeDia() |
| | | }) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>ç¨æ°ç®¡çç³»ç»</h2> |
| | | <div class="header-info"> |
| | | <span class="update-time">æåæ´æ°ï¼{{ lastUpdateTime }}</span> |
| | | <el-button type="primary" size="small" @click="refreshData"> |
| | | <el-icon><Refresh /></el-icon> |
| | | å·æ°æ°æ® |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç»è®¡å¡çåºå --> |
| | | <div class="stats-cards"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon gas-device"> |
| | | <el-icon size="32"><Box /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-value">{{ totalDevices }}</div> |
| | | <div class="stat-label">å¨ç¨è®¾å¤</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon daily-consumption"> |
| | | <el-icon size="32"><TrendCharts /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-value">{{ dailyConsumption }} m³</div> |
| | | <div class="stat-label">æ¥èé</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon monthly-consumption"> |
| | | <el-icon size="32"><DataLine /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-value">{{ monthlyConsumption }} m³</div> |
| | | <div class="stat-label">æèé</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon gas-price"> |
| | | <el-icon size="32"><Money /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-value">Â¥{{ gasUnitPrice }}</div> |
| | | <div class="stat-label">æ°ä½åä»·</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- è´¹ç¨ç»è®¡åºå --> |
| | | <div class="cost-stats"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-card class="cost-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æ¥è´¹ç¨ç»è®¡</span> |
| | | <el-tag type="success" size="small">仿¥</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="cost-content"> |
| | | <div class="cost-main"> |
| | | <span class="cost-amount">Â¥{{ dailyTotalCost.toFixed(2) }}</span> |
| | | <span class="cost-unit">å
</span> |
| | | </div> |
| | | <div class="cost-details"> |
| | | <div class="cost-item"> |
| | | <span>æ¶èéï¼</span> |
| | | <span>{{ dailyConsumption }} m³</span> |
| | | </div> |
| | | <div class="cost-item"> |
| | | <span>åä»·ï¼</span> |
| | | <span>¥{{ gasUnitPrice }}/m³</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-card class="cost-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æè´¹ç¨ç»è®¡</span> |
| | | <el-tag type="primary" size="small">æ¬æ</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="cost-content"> |
| | | <div class="cost-main"> |
| | | <span class="cost-amount">Â¥{{ monthlyTotalCost.toFixed(2) }}</span> |
| | | <span class="cost-unit">å
</span> |
| | | </div> |
| | | <div class="cost-details"> |
| | | <div class="cost-item"> |
| | | <span>æ¶èéï¼</span> |
| | | <span>{{ monthlyConsumption }} m³</span> |
| | | </div> |
| | | <div class="cost-item"> |
| | | <span>å¹³ååä»·ï¼</span> |
| | | <span>¥{{ gasUnitPrice }}/m³</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- 设å¤å表åºå --> |
| | | <div class="device-section"> |
| | | <el-card> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>设å¤çæ§</span> |
| | | <div class="header-actions"> |
| | | <el-button type="primary" size="small" @click="addDevice"> |
| | | <el-icon><Plus /></el-icon> |
| | | æ·»å è®¾å¤ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <el-table :data="deviceList" border style="width: 100%" v-loading="tableLoading" stripe> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="设å¤ç¼å·" prop="deviceCode" width="120" show-overflow-tooltip /> |
| | | <el-table-column label="设å¤åç§°" prop="deviceName" width="150" show-overflow-tooltip /> |
| | | <el-table-column label="设å¤ç±»å" prop="deviceType" width="120" show-overflow-tooltip /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specification" width="150" show-overflow-tooltip /> |
| | | <el-table-column label="å½ååå(MPa)" prop="currentPressure" width="130" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <span :class="getPressureClass(scope.row.currentPressure)"> |
| | | {{ scope.row.currentPressure }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å½å温度(â)" prop="currentTemperature" width="130" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <span :class="getTemperatureClass(scope.row.currentTemperature)"> |
| | | {{ scope.row.currentTemperature }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æ°ä½æµåº¦(ppm)" prop="gasConcentration" width="140" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <span :class="getConcentrationClass(scope.row.gasConcentration)"> |
| | | {{ scope.row.gasConcentration }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="è¿è¡ç¶æ" prop="status" width="100" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)" size="small"> |
| | | {{ getStatusText(scope.row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æåæ´æ°" prop="lastUpdate" width="160" show-overflow-tooltip /> |
| | | <el-table-column label="æä½" align="center" width="100" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button link size="small" @click="editDevice(scope.row)"> |
| | | ç¼è¾ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- æ·»å /ç¼è¾è®¾å¤å¼¹çª --> |
| | | <el-dialog v-model="deviceDialogVisible" :title="dialogTitle" width="600px"> |
| | | <el-form :model="deviceForm" :rules="deviceRules" ref="deviceFormRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设å¤ç¼å·" prop="deviceCode"> |
| | | <el-input v-model="deviceForm.deviceCode" placeholder="请è¾å
¥è®¾å¤ç¼å·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设å¤åç§°" prop="deviceName"> |
| | | <el-input v-model="deviceForm.deviceName" placeholder="请è¾å
¥è®¾å¤åç§°" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设å¤ç±»å" prop="deviceType"> |
| | | <el-select v-model="deviceForm.deviceType" placeholder="è¯·éæ©è®¾å¤ç±»å" style="width: 100%"> |
| | | <el-option label="æ¶²åæ°å¨ç½" value="æ¶²åæ°å¨ç½" /> |
| | | <el-option label="å缩æ°å¨ç½" value="å缩æ°å¨ç½" /> |
| | | <el-option label="å¤©ç¶æ°å¨ç½" value="å¤©ç¶æ°å¨ç½" /> |
| | | <el-option label="æ°§æ°å¨ç½" value="æ°§æ°å¨ç½" /> |
| | | <el-option label="å
¶ä»" value="å
¶ä»" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·" prop="specification"> |
| | | <el-input v-model="deviceForm.specification" placeholder="请è¾å
¥è§æ ¼åå·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设计åå(MPa)" prop="designPressure"> |
| | | <el-input-number v-model="deviceForm.designPressure" :min="0" :precision="2" style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="容积(m³)" prop="volume"> |
| | | <el-input-number v-model="deviceForm.volume" :min="0" :precision="2" style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="deviceDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="saveDevice">ä¿å</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onUnmounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { |
| | | Refresh, |
| | | Box, |
| | | TrendCharts, |
| | | DataLine, |
| | | Money, |
| | | Plus |
| | | } from '@element-plus/icons-vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const lastUpdateTime = ref('') |
| | | const totalDevices = ref(0) |
| | | const dailyConsumption = ref(0) |
| | | const monthlyConsumption = ref(0) |
| | | const gasUnitPrice = ref(0) |
| | | const dailyTotalCost = ref(0) |
| | | const monthlyTotalCost = ref(0) |
| | | const deviceList = ref([]) |
| | | const tableLoading = ref(false) |
| | | const deviceDialogVisible = ref(false) |
| | | const dialogTitle = ref('') |
| | | const deviceFormRef = ref() |
| | | |
| | | // 设å¤è¡¨åæ°æ® |
| | | const deviceForm = reactive({ |
| | | deviceCode: '', |
| | | deviceName: '', |
| | | deviceType: '', |
| | | specification: '', |
| | | designPressure: 0, |
| | | volume: 0 |
| | | }) |
| | | |
| | | // 表åéªè¯è§å |
| | | const deviceRules = { |
| | | deviceCode: [{ required: true, message: '请è¾å
¥è®¾å¤ç¼å·', trigger: 'blur' }], |
| | | deviceName: [{ required: true, message: '请è¾å
¥è®¾å¤åç§°', trigger: 'blur' }], |
| | | deviceType: [{ required: true, message: 'è¯·éæ©è®¾å¤ç±»å', trigger: 'change' }], |
| | | specification: [{ required: true, message: '请è¾å
¥è§æ ¼åå·', trigger: 'blur' }], |
| | | designPressure: [{ required: true, message: '请è¾å
¥è®¾è®¡åå', trigger: 'blur' }], |
| | | volume: [{ required: true, message: '请è¾å
¥å®¹ç§¯', trigger: 'blur' }] |
| | | } |
| | | |
| | | // 宿¶å¨ |
| | | let updateTimer = null |
| | | |
| | | // æ¨¡ææ°æ®çæ |
| | | const generateMockData = () => { |
| | | // æ´æ°ç»è®¡æ°æ® |
| | | totalDevices.value = Math.floor(Math.random() * 10) + 15 // 15-25å°è®¾å¤ |
| | | dailyConsumption.value = Math.floor(Math.random() * 100) + 200 // 200-300 m³ |
| | | monthlyConsumption.value = Math.floor(Math.random() * 2000) + 5000 // 5000-7000 m³ |
| | | gasUnitPrice.value = (Math.random() * 2 + 3).toFixed(2) // 3-5å
/m³ |
| | | |
| | | // 计ç®è´¹ç¨ |
| | | dailyTotalCost.value = dailyConsumption.value * gasUnitPrice.value |
| | | monthlyTotalCost.value = monthlyConsumption.value * gasUnitPrice.value |
| | | |
| | | // æ´æ°è®¾å¤åè¡¨æ°æ® |
| | | deviceList.value = Array.from({ length: totalDevices.value }, (_, index) => ({ |
| | | id: index + 1, |
| | | deviceCode: `GT${String(index + 1).padStart(3, '0')}`, |
| | | deviceName: `卿°ç½${index + 1}`, |
| | | deviceType: ['æ¶²åæ°å¨ç½', 'å缩æ°å¨ç½', 'å¤©ç¶æ°å¨ç½', 'æ°§æ°å¨ç½'][Math.floor(Math.random() * 4)], |
| | | specification: `${Math.floor(Math.random() * 50) + 50}m³`, |
| | | currentPressure: (Math.random() * 2 + 0.5).toFixed(2), |
| | | currentTemperature: (Math.random() * 20 + 15).toFixed(1), |
| | | gasConcentration: (Math.random() * 10).toFixed(2), |
| | | status: ['running', 'stopped', 'warning', 'error'][Math.floor(Math.random() * 4)], |
| | | lastUpdate: new Date().toLocaleString() |
| | | })) |
| | | |
| | | // æ´æ°æåæ´æ°æ¶é´ |
| | | lastUpdateTime.value = new Date().toLocaleString() |
| | | } |
| | | |
| | | // è·åååç¶ææ ·å¼ |
| | | const getPressureClass = (pressure) => { |
| | | const p = parseFloat(pressure) |
| | | if (p < 0.8) return 'pressure-low' |
| | | if (p > 1.5) return 'pressure-high' |
| | | return 'pressure-normal' |
| | | } |
| | | |
| | | // è·åæ¸©åº¦ç¶ææ ·å¼ |
| | | const getTemperatureClass = (temperature) => { |
| | | const t = parseFloat(temperature) |
| | | if (t < 10 || t > 35) return 'temperature-warning' |
| | | return 'temperature-normal' |
| | | } |
| | | |
| | | // è·åæµåº¦ç¶ææ ·å¼ |
| | | const getConcentrationClass = (concentration) => { |
| | | const c = parseFloat(concentration) |
| | | if (c > 5) return 'concentration-warning' |
| | | return 'concentration-normal' |
| | | } |
| | | |
| | | // è·åç¶æç±»å |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | running: 'success', |
| | | stopped: 'info', |
| | | warning: 'warning', |
| | | error: 'danger' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | // è·åç¶æææ¬ |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | running: 'è¿è¡ä¸', |
| | | stopped: '已忢', |
| | | warning: 'è¦å', |
| | | error: 'æ
é' |
| | | } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | // å·æ°æ°æ® |
| | | const refreshData = () => { |
| | | generateMockData() |
| | | ElMessage.success('æ°æ®å·²å·æ°') |
| | | } |
| | | |
| | | // æ·»å è®¾å¤ |
| | | const addDevice = () => { |
| | | dialogTitle.value = 'æ·»å 设å¤' |
| | | Object.keys(deviceForm).forEach(key => { |
| | | deviceForm[key] = key === 'designPressure' || key === 'volume' ? 0 : '' |
| | | }) |
| | | deviceDialogVisible.value = true |
| | | } |
| | | |
| | | // ç¼è¾è®¾å¤ |
| | | const editDevice = (row) => { |
| | | dialogTitle.value = 'ç¼è¾è®¾å¤' |
| | | Object.keys(deviceForm).forEach(key => { |
| | | if (row[key] !== undefined) { |
| | | deviceForm[key] = row[key] |
| | | } |
| | | }) |
| | | deviceDialogVisible.value = true |
| | | } |
| | | |
| | | |
| | | |
| | | // ä¿åè®¾å¤ |
| | | const saveDevice = () => { |
| | | deviceFormRef.value.validate((valid) => { |
| | | if (valid) { |
| | | ElMessage.success('ä¿åæå') |
| | | deviceDialogVisible.value = false |
| | | refreshData() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | |
| | | |
| | | // å¯å¨å®æ¶æ´æ° |
| | | const startAutoUpdate = () => { |
| | | updateTimer = setInterval(() => { |
| | | generateMockData() |
| | | }, 60000) // æ¯åéæ´æ°ä¸æ¬¡ |
| | | } |
| | | |
| | | // 忢宿¶æ´æ° |
| | | const stopAutoUpdate = () => { |
| | | if (updateTimer) { |
| | | clearInterval(updateTimer) |
| | | updateTimer = null |
| | | } |
| | | } |
| | | |
| | | // ç»ä»¶æè½½ |
| | | onMounted(() => { |
| | | generateMockData() |
| | | startAutoUpdate() |
| | | }) |
| | | |
| | | // ç»ä»¶å¸è½½ |
| | | onUnmounted(() => { |
| | | stopAutoUpdate() |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | background: #f5f5f5; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | padding: 20px; |
| | | background: white; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | |
| | | h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | font-size: 24px; |
| | | } |
| | | |
| | | .header-info { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 15px; |
| | | |
| | | .update-time { |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .stats-cards { |
| | | margin-bottom: 20px; |
| | | |
| | | .stat-card { |
| | | .stat-content { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 10px; |
| | | |
| | | .stat-icon { |
| | | width: 60px; |
| | | height: 60px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-right: 15px; |
| | | |
| | | &.gas-device { |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | color: white; |
| | | } |
| | | |
| | | &.daily-consumption { |
| | | background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); |
| | | color: white; |
| | | } |
| | | |
| | | &.monthly-consumption { |
| | | background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); |
| | | color: white; |
| | | } |
| | | |
| | | &.gas-price { |
| | | background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); |
| | | color: white; |
| | | } |
| | | } |
| | | |
| | | .stat-info { |
| | | .stat-value { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .stat-label { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | margin-top: 5px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .cost-stats { |
| | | margin-bottom: 20px; |
| | | |
| | | .cost-card { |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .cost-content { |
| | | text-align: center; |
| | | padding: 20px 0; |
| | | |
| | | .cost-main { |
| | | margin-bottom: 15px; |
| | | |
| | | .cost-amount { |
| | | font-size: 36px; |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .cost-unit { |
| | | font-size: 16px; |
| | | color: #909399; |
| | | margin-left: 5px; |
| | | } |
| | | } |
| | | |
| | | .cost-details { |
| | | .cost-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 8px; |
| | | font-size: 14px; |
| | | color: #606266; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .device-section { |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // ç¶ææ ·å¼ |
| | | .pressure-low { |
| | | color: #e6a23c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .pressure-normal { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .pressure-high { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .temperature-normal { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .temperature-warning { |
| | | color: #e6a23c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .concentration-normal { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .concentration-warning { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <div slot="header" class="clearfix"> |
| | | <span>çµè¡¨éé管ç</span> |
| | | <el-button style="float: right; padding: 3px 0" link @click="refreshData"> |
| | | <i class="el-icon-refresh"></i> å·æ° |
| | | </el-button> |
| | | </div> |
| | | |
| | | <!-- æµè¯æé® --> |
| | | <el-row :gutter="20" style="margin-bottom: 15px;"> |
| | | <el-col :span="24"> |
| | | <el-button @click="addTestData" type="primary" size="small">æ·»å æµè¯æ°æ®</el-button> |
| | | <el-button @click="clearData" type="danger" size="small">æ¸
ç©ºæ°æ®</el-button> |
| | | <el-button @click="testChart" type="success" size="small">æµè¯å¾è¡¨</el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="6"> |
| | | <el-input |
| | | v-model="searchForm.meterNo" |
| | | placeholder="请è¾å
¥çµè¡¨ç¼å·" |
| | | clearable |
| | | @keyup.enter.native="handleSearch" |
| | | > |
| | | <i slot="prefix" class="el-input__icon el-icon-search"></i> |
| | | </el-input> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.location" placeholder="è¯·éæ©ä½ç½®" clearable> |
| | | <el-option label="ç产车é´A" value="车é´A"></el-option> |
| | | <el-option label="ç产车é´B" value="车é´B"></el-option> |
| | | <el-option label="åå
¬åºå" value="åå
¬åº"></el-option> |
| | | <el-option label="é
çµå®¤" value="é
çµå®¤"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-date-picker |
| | | v-model="searchForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | format="yyyy-MM-dd" |
| | | value-format="yyyy-MM-dd" |
| | | /> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- çµè¡¨å表 --> |
| | | <el-table |
| | | :data="meterList" |
| | | style="width: 100%" |
| | | v-loading="loading" |
| | | border |
| | | stripe |
| | | height="calc(100vh - 22em)" |
| | | > |
| | | <el-table-column prop="meterNo" label="çµè¡¨ç¼å·" width="120" /> |
| | | <el-table-column prop="location" label="å®è£
ä½ç½®" width="120" /> |
| | | <el-table-column prop="meterType" label="çµè¡¨ç±»å" width="120" /> |
| | | <el-table-column prop="voltage" label="çµåç级" width="100" /> |
| | | <el-table-column prop="currentReading" label="å½å读æ°(kWh)" width="140" /> |
| | | <el-table-column prop="lastReading" label="䏿¬¡è¯»æ°(kWh)" width="140" /> |
| | | <el-table-column prop="consumption" label="ç¨çµé(kWh)" width="120" /> |
| | | <el-table-column prop="power" label="åç(kW)" width="100" /> |
| | | <el-table-column prop="powerFactor" label="åçå æ°" width="100" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="80"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'æ£å¸¸' ? 'success' : 'danger'"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="lastUpdateTime" label="æåæ´æ°æ¶é´" width="160" /> |
| | | <el-table-column label="æä½" width="180" fixed="right" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link @click="viewDetails(scope.row)"> |
| | | æ¥ç详æ
|
| | | </el-button> |
| | | <el-button link @click="manualCollection(scope.row)"> |
| | | æå¨éé |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <!-- å页 --> |
| | | <pagination |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pagination.currentPage" |
| | | :limit="pagination.pageSize" |
| | | @pagination="handleCurrentChange" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- 详æ
å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | title="çµè¡¨è¯¦æ
" |
| | | v-model="detailDialogVisible" |
| | | width="60%" |
| | | @opened="onDialogOpened" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>çµè¡¨ç¼å·:</label> |
| | | <span>{{ currentMeter.meterNo }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>å®è£
ä½ç½®:</label> |
| | | <span>{{ currentMeter.location }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>çµè¡¨ç±»å:</label> |
| | | <span>{{ currentMeter.meterType }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>çµåç级:</label> |
| | | <span>{{ currentMeter.voltage }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>å½å读æ°:</label> |
| | | <span>{{ currentMeter.currentReading }} kWh</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>䏿¬¡è¯»æ°:</label> |
| | | <span>{{ currentMeter.lastReading }} kWh</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>ç¨çµé:</label> |
| | | <span>{{ currentMeter.consumption }} kWh</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>åç:</label> |
| | | <span>{{ currentMeter.power }} kW</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>åçå æ°:</label> |
| | | <span>{{ currentMeter.powerFactor }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>ç¶æ:</label> |
| | | <el-tag :type="currentMeter.status === 'æ£å¸¸' ? 'success' : 'danger'"> |
| | | {{ currentMeter.status }} |
| | | </el-tag> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="detail-item"> |
| | | <label>æåæ´æ°æ¶é´:</label> |
| | | <span>{{ currentMeter.lastUpdateTime }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- ç¨çµè¶å¿å¾ --> |
| | | <div style="margin-top: 20px;"> |
| | | <h4>24å°æ¶ç¨çµè¶å¿</h4> |
| | | <div ref="chartContainer" style="height: 300px;"></div> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import * as echarts from 'echarts' |
| | | |
| | | export default { |
| | | name: 'MeterCollection', |
| | | data() { |
| | | return { |
| | | loading: false, |
| | | searchForm: { |
| | | meterNo: '', |
| | | location: '', |
| | | dateRange: [] |
| | | }, |
| | | meterList: [], |
| | | pagination: { |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0 |
| | | }, |
| | | detailDialogVisible: false, |
| | | currentMeter: {}, |
| | | chart: null |
| | | } |
| | | }, |
| | | created() { |
| | | // ç«å³çæä¸äºæµè¯æ°æ® |
| | | this.meterList = [ |
| | | { |
| | | id: 1, |
| | | meterNo: 'M001', |
| | | location: '车é´A', |
| | | meterType: 'æºè½çµè¡¨', |
| | | voltage: '380V', |
| | | currentReading: 8500, |
| | | lastReading: 8400, |
| | | consumption: 100, |
| | | power: '75.5', |
| | | powerFactor: '0.85', |
| | | status: 'æ£å¸¸', |
| | | lastUpdateTime: '2024-01-15 10:30:00' |
| | | }, |
| | | { |
| | | id: 2, |
| | | meterNo: 'M002', |
| | | location: '车é´B', |
| | | meterType: 'å¤åè½çµè¡¨', |
| | | voltage: '220V', |
| | | currentReading: 6200, |
| | | lastReading: 6100, |
| | | consumption: 100, |
| | | power: '45.2', |
| | | powerFactor: '0.92', |
| | | status: 'æ£å¸¸', |
| | | lastUpdateTime: '2024-01-15 10:25:00' |
| | | } |
| | | ] |
| | | this.pagination.total = this.meterList.length |
| | | }, |
| | | mounted() { |
| | | // å»¶è¿ä¸ç¹æ¶é´åè°ç¨ï¼ç¡®ä¿DOMå·²ç»æ¸²æ |
| | | this.$nextTick(() => { |
| | | this.getMeterList() |
| | | }) |
| | | }, |
| | | watch: { |
| | | meterList: { |
| | | handler(newVal) { |
| | | console.log('meterListæ°æ®åå:', newVal) |
| | | }, |
| | | deep: true, |
| | | immediate: true |
| | | } |
| | | }, |
| | | methods: { |
| | | // è·åçµè¡¨å表 |
| | | getMeterList() { |
| | | this.loading = true |
| | | // 模æAPIè°ç¨ |
| | | setTimeout(() => { |
| | | const mockData = this.generateMockData() |
| | | this.meterList = mockData |
| | | this.pagination.total = this.meterList.length |
| | | this.loading = false |
| | | }, 500) |
| | | }, |
| | | |
| | | // çææ¨¡ææ°æ® |
| | | generateMockData() { |
| | | const locations = ['车é´A', '车é´B', 'åå
¬åº', 'é
çµå®¤'] |
| | | const meterTypes = ['æºè½çµè¡¨', 'å¤åè½çµè¡¨', 'æ®éçµè¡¨'] |
| | | const voltages = ['220V', '380V', '10kV'] |
| | | const statuses = ['æ£å¸¸', 'å¼å¸¸'] |
| | | |
| | | const data = [] |
| | | for (let i = 1; i <= 25; i++) { |
| | | const currentReading = Math.floor(Math.random() * 10000) + 5000 |
| | | const lastReading = currentReading - Math.floor(Math.random() * 100) - 10 |
| | | const consumption = currentReading - lastReading |
| | | const power = Math.random() * 100 + 20 |
| | | const powerFactor = (Math.random() * 0.3 + 0.7).toFixed(2) |
| | | |
| | | data.push({ |
| | | id: i, |
| | | meterNo: `M${String(i).padStart(3, '0')}`, |
| | | location: locations[Math.floor(Math.random() * locations.length)], |
| | | meterType: meterTypes[Math.floor(Math.random() * meterTypes.length)], |
| | | voltage: voltages[Math.floor(Math.random() * voltages.length)], |
| | | currentReading: currentReading, |
| | | lastReading: lastReading, |
| | | consumption: consumption, |
| | | power: power.toFixed(2), |
| | | powerFactor: powerFactor, |
| | | status: statuses[Math.floor(Math.random() * statuses.length)], |
| | | lastUpdateTime: this.formatDate(new Date(Date.now() - Math.random() * 86400000)) |
| | | }) |
| | | } |
| | | return data |
| | | }, |
| | | |
| | | // æ ¼å¼åæ¥æ |
| | | formatDate(date) { |
| | | const year = date.getFullYear() |
| | | const month = String(date.getMonth() + 1).padStart(2, '0') |
| | | const day = String(date.getDate()).padStart(2, '0') |
| | | const hours = String(date.getHours()).padStart(2, '0') |
| | | const minutes = String(date.getMinutes()).padStart(2, '0') |
| | | return `${year}-${month}-${day} ${hours}:${minutes}` |
| | | }, |
| | | |
| | | // æç´¢ |
| | | handleSearch() { |
| | | this.pagination.currentPage = 1 |
| | | this.getMeterList() |
| | | }, |
| | | |
| | | // éç½®æç´¢ |
| | | resetSearch() { |
| | | this.searchForm = { |
| | | meterNo: '', |
| | | location: '', |
| | | dateRange: [] |
| | | } |
| | | this.handleSearch() |
| | | }, |
| | | |
| | | // æ¥ç详æ
|
| | | viewDetails(row) { |
| | | this.currentMeter = row |
| | | this.detailDialogVisible = true |
| | | }, |
| | | |
| | | // å¯¹è¯æ¡æå¼ååå§åå¾è¡¨ |
| | | onDialogOpened() { |
| | | this.$nextTick(() => { |
| | | setTimeout(() => { |
| | | this.initChart() |
| | | }, 100) |
| | | }) |
| | | }, |
| | | |
| | | // æå¨éé |
| | | manualCollection(row) { |
| | | this.$message.success(`æ£å¨ééçµè¡¨ ${row.meterNo} çæ°æ®...`) |
| | | // 模æééè¿ç¨ |
| | | setTimeout(() => { |
| | | row.currentReading = Math.floor(Math.random() * 100) + row.currentReading |
| | | row.lastUpdateTime = this.formatDate(new Date()) |
| | | this.$message.success('æ°æ®éé宿') |
| | | }, 1000) |
| | | }, |
| | | |
| | | // å·æ°æ°æ® |
| | | refreshData() { |
| | | this.getMeterList() |
| | | this.$message.success('æ°æ®å·²å·æ°') |
| | | }, |
| | | |
| | | // æ·»å æµè¯æ°æ® |
| | | addTestData() { |
| | | const testData = { |
| | | id: Date.now(), |
| | | meterNo: `M${String(this.meterList.length + 1).padStart(3, '0')}`, |
| | | location: 'æµè¯ä½ç½®', |
| | | meterType: 'æµè¯çµè¡¨', |
| | | voltage: '220V', |
| | | currentReading: Math.floor(Math.random() * 10000) + 1000, |
| | | lastReading: Math.floor(Math.random() * 5000) + 500, |
| | | consumption: Math.floor(Math.random() * 100) + 10, |
| | | power: (Math.random() * 100 + 10).toFixed(2), |
| | | powerFactor: (Math.random() * 0.3 + 0.7).toFixed(2), |
| | | status: 'æ£å¸¸', |
| | | lastUpdateTime: this.formatDate(new Date()) |
| | | } |
| | | this.meterList.push(testData) |
| | | this.pagination.total = this.meterList.length |
| | | this.$message.success('æµè¯æ°æ®å·²æ·»å ') |
| | | }, |
| | | |
| | | // æ¸
ç©ºæ°æ® |
| | | clearData() { |
| | | this.meterList = [] |
| | | this.pagination.total = 0 |
| | | this.$message.success('æ°æ®å·²æ¸
空') |
| | | }, |
| | | |
| | | // æµè¯å¾è¡¨ |
| | | testChart() { |
| | | this.$message.info('å¾è¡¨æµè¯åè½') |
| | | // å建ä¸ä¸ªæµè¯å¯¹è¯æ¡æ¥æµè¯å¾è¡¨ |
| | | this.currentMeter = { |
| | | meterNo: 'TEST001', |
| | | location: 'æµè¯ä½ç½®', |
| | | meterType: 'æµè¯çµè¡¨', |
| | | voltage: '220V', |
| | | currentReading: 1000, |
| | | lastReading: 900, |
| | | consumption: 100, |
| | | power: '50.0', |
| | | powerFactor: '0.85', |
| | | status: 'æ£å¸¸', |
| | | lastUpdateTime: '2024-01-15 12:00:00' |
| | | } |
| | | this.detailDialogVisible = true |
| | | }, |
| | | |
| | | // å页大尿¹å |
| | | handleSizeChange(val) { |
| | | this.pagination.pageSize = val |
| | | this.getMeterList() |
| | | }, |
| | | |
| | | // å½å页æ¹å |
| | | handleCurrentChange(val) { |
| | | this.pagination.pageSize = val.limit |
| | | this.pagination.currentPage = val.page |
| | | this.getMeterList() |
| | | }, |
| | | |
| | | // åå§åå¾è¡¨ |
| | | initChart() { |
| | | try { |
| | | if (this.chart) { |
| | | this.chart.dispose() |
| | | this.chart = null |
| | | } |
| | | |
| | | // ç¡®ä¿DOMå
ç´ åå¨ |
| | | if (!this.$refs.chartContainer) { |
| | | console.error('å¾è¡¨å®¹å¨ä¸åå¨ï¼çå¾
DOMæ´æ°...') |
| | | // 妿容å¨ä¸åå¨ï¼çå¾
ä¸ä¸åè¯ |
| | | setTimeout(() => { |
| | | this.initChart() |
| | | }, 100) |
| | | return |
| | | } |
| | | |
| | | // æ£æ¥å®¹å¨å°ºå¯¸ |
| | | const container = this.$refs.chartContainer |
| | | if (container.offsetWidth === 0 || container.offsetHeight === 0) { |
| | | setTimeout(() => { |
| | | this.initChart() |
| | | }, 100) |
| | | return |
| | | } |
| | | this.chart = echarts.init(container) |
| | | |
| | | // çæ24å°æ¶æ¨¡ææ°æ® |
| | | const hours = [] |
| | | const consumption = [] |
| | | for (let i = 0; i < 24; i++) { |
| | | hours.push(`${i}:00`) |
| | | consumption.push(Math.floor(Math.random() * 50) + 20) |
| | | } |
| | | |
| | | const option = { |
| | | title: { |
| | | text: '24å°æ¶ç¨çµéè¶å¿', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | formatter: '{b}<br/>ç¨çµé: {c} kWh' |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: hours, |
| | | axisLabel: { |
| | | rotate: 45 |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'ç¨çµé (kWh)' |
| | | }, |
| | | series: [{ |
| | | data: consumption, |
| | | type: 'line', |
| | | smooth: true, |
| | | areaStyle: { |
| | | opacity: 0.3 |
| | | }, |
| | | itemStyle: { |
| | | color: '#409EFF' |
| | | } |
| | | }] |
| | | } |
| | | |
| | | this.chart.setOption(option) |
| | | } catch (error) { |
| | | console.error('å¾è¡¨åå§å失败:', error) |
| | | this.$message.error('å¾è¡¨åå§å失败: ' + error.message) |
| | | } |
| | | } |
| | | }, |
| | | |
| | | beforeUnmount() { |
| | | if (this.chart) { |
| | | try { |
| | | this.chart.dispose() |
| | | this.chart = null |
| | | } catch (error) { |
| | | console.error('æ¸
çå¾è¡¨å¤±è´¥:', error) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .pagination { |
| | | margin-top: 20px; |
| | | text-align: right; |
| | | } |
| | | |
| | | .el-table { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .detail-item { |
| | | margin-bottom: 15px; |
| | | padding: 10px; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 4px; |
| | | background-color: #fafafa; |
| | | } |
| | | |
| | | .detail-item label { |
| | | font-weight: bold; |
| | | color: #606266; |
| | | margin-right: 10px; |
| | | min-width: 100px; |
| | | display: inline-block; |
| | | } |
| | | |
| | | .detail-item span { |
| | | color: #303133; |
| | | } |
| | | |
| | | .detail-item .el-tag { |
| | | margin-left: 0; |
| | | } |
| | | </style> |
| | |
| | | > |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设å¤ï¼" prop="code"> |
| | | <el-form-item label="设å¤ï¼" prop="deviceModel"> |
| | | <el-select |
| | | v-model="form.code" |
| | | v-model="form.deviceModel" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | @change="setName" |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯æ¥éå¶æ°´éï¼" prop="everyNum"> |
| | | <el-form-item label="æ¯æ¥éå¶æ°´éï¼" prop="waterDayLimit"> |
| | | <el-input |
| | | v-model="form.everyNum" |
| | | v-model="form.waterDayLimit" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¢å®æµéï¼" prop="flowRating"> |
| | | <el-form-item label="é¢å®æµéï¼" prop="ratedRate"> |
| | | <el-input |
| | | v-model="form.flowRating" |
| | | v-model="form.ratedRate" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å®é
æµéï¼" prop="flowActual"> |
| | | <el-form-item label="å®é
æµéï¼" prop="actualTraffic"> |
| | | <el-input |
| | | v-model="form.flowActual" |
| | | v-model="form.actualTraffic" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¿è¡æ¶é´ï¼" prop="runDate"> |
| | | <el-form-item label="è¿è¡æ¶é´ï¼" prop="runTime"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | | v-model="form.runDate" |
| | | v-model="form.runTime" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="彿¥ç¨æ°´éï¼" prop="dayNum"> |
| | | <el-form-item label="彿¥ç¨æ°´éï¼" prop="waterDay"> |
| | | <el-input |
| | | v-model="form.dayNum" |
| | | v-model="form.waterDay" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¨æ°´ç±»åï¼" prop="waterType"> |
| | | <el-form-item label="ç¨æ°´ç±»åï¼" prop="type"> |
| | | <el-select |
| | | v-model="form.waterType" |
| | | v-model="form.type" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | > |
| | |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | | name: "", |
| | | code: "", |
| | | everyNum: "", |
| | | flowRating: "", |
| | | flowActual: "", |
| | | runDate: "", |
| | | dayNum: "", |
| | | deviceName: "", |
| | | deviceModel: "", |
| | | waterDayLimit: "", |
| | | ratedRate: "", |
| | | actualTraffic: "", |
| | | runTime: "", |
| | | waterDay: "", |
| | | waterPrice: "", |
| | | waterType: "", |
| | | type: "", |
| | | }, |
| | | rules: { |
| | | code: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | runDate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | everyNum: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | flowRating: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | flowActual: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | dayNum: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | deviceModel: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | runTime: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | waterDayLimit: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | ratedRate: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | actualTraffic: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | waterDay: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | waterPrice: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | waterType: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | type: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | }, |
| | | }) |
| | | const { form, rules } = toRefs(data); |
| | |
| | | dialogFormVisible.value = true; |
| | | form.value = {} |
| | | proxy.resetForm("formRef"); |
| | | waterDeviceList().then((res) => { |
| | | codeList.value = res.data; |
| | | waterDeviceList({size: -1}).then((res) => { |
| | | codeList.value = res.data.records; |
| | | }); |
| | | if (type === "edit") { |
| | | form.value = {...row} |
| | |
| | | <div> |
| | | <span class="search_title">设å¤åç§°ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.name" |
| | | v-model="searchForm.deviceName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | @change="handleQuery" |
| | |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "设å¤åç§°", |
| | | prop: "name", |
| | | prop: "deviceName", |
| | | width: 200, |
| | | }, |
| | | { |
| | | label: "è§æ ¼åå·", |
| | | prop: "code", |
| | | prop: "deviceModel", |
| | | width: 200, |
| | | }, |
| | | { |
| | | label: "é¢å®æµé", |
| | | prop: "flowRating", |
| | | prop: "ratedRate", |
| | | }, |
| | | { |
| | | label: "å®é
æµé", |
| | | prop: "flowActual", |
| | | prop: "actualTraffic", |
| | | }, |
| | | { |
| | | label: "è¿è¡æ¶é´", |
| | | prop: "runDate", |
| | | prop: "runTime", |
| | | width:150 |
| | | }, |
| | | { |
| | | label: "彿¥ç¨æ°´é", |
| | | prop: "dayNum", |
| | | prop: "waterDay", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "æ¯æ¥éå¶æ°´é", |
| | | prop: "everyNum", |
| | | prop: "waterDayLimit", |
| | | width:220 |
| | | }, |
| | | { |
| | |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/waterEquipmentConsumption/importData", |
| | | url: import.meta.env.VITE_APP_BASE_API + "/waterRecord/importData", |
| | | // æä»¶ä¸ä¼ åçåè° |
| | | beforeUpload: (file) => { |
| | | console.log('æä»¶å³å°ä¸ä¼ ', file); |
| | |
| | | } |
| | | function importTemplate() { |
| | | proxy.download( |
| | | "/waterEquipmentConsumption/export", |
| | | "/waterRecord/export", |
| | | {}, |
| | | 'ç¨æ°´è®¾å¤å¯¼å
¥æ¨¡ç.xlsx' |
| | | ); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container iot-monitor"> |
| | | <div class="header"> |
| | | <div class="title">宿¶å·¥åµçæ§ï¼IoTï¼</div> |
| | | <div class="actions"> |
| | | <el-button type="primary" @click="toggleCollecting">{{ collecting ? 'æåéé' : 'å¯å¨éé' }}</el-button> |
| | | <el-button @click="resetAll">éç½®</el-button> |
| | | <span class="ts">䏿¬¡æ´æ°æ¶é´ï¼{{ lastUpdatedDisplay }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- <el-alert--> |
| | | <!-- title="è¾¹ç¼é¢è¦è§åï¼è½´æ¿ç£¨æ-æ¯å¨å¼å离åºçº¿Â±5%触ååè¦ï¼æ¸©åº¦/ååè¶ç触åæé"--> |
| | | <!-- type="info"--> |
| | | <!-- :closable="false"--> |
| | | <!-- show-icon--> |
| | | <!-- class="rule-alert"--> |
| | | <!-- />--> |
| | | |
| | | <el-row :gutter="16"> |
| | | <el-col v-for="dev in devices" :key="dev.id" :span="12"> |
| | | <el-card :class="['device-card', dev.hasAlert ? 'is-alert' : '']"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <div class="card-title"> |
| | | <span class="device-name">{{ dev.name }}</span> |
| | | <el-tag :type="dev.hasAlert ? 'danger' : 'success'" size="small">{{ dev.hasAlert ? 'åè¦' : 'æ£å¸¸' }}</el-tag> |
| | | </div> |
| | | <div class="meta">ç±»åï¼{{ dev.type }}ï½åºçº¿æ¯å¨ï¼{{ dev.baseline.vibration.toFixed(2) }} mm/s</div> |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="metrics"> |
| | | <div class="metric" :class="{ 'metric-alert': dev.alerts.vibration }"> |
| | | <div class="metric-head"> |
| | | <span>æ¯å¨(mm/s)</span> |
| | | <el-tag :type="dev.alerts.vibration ? 'danger' : 'info'" size="small">{{ dev.alerts.vibration ? '±5%è¶ç' : 'åºçº¿Â±5%' }}</el-tag> |
| | | </div> |
| | | <div class="metric-value">{{ currentValue(dev.series.vibration).toFixed(2) }}</div> |
| | | <Echarts |
| | | :xAxis="[{ type: 'category', data: xAxisLabels }]" |
| | | :yAxis="[{ type: 'value', name: 'mm/s' }]" |
| | | :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.vibration }]" |
| | | :tooltip="{ trigger: 'axis' }" |
| | | :grid="{ left: 40, right: 10, top: 10, bottom: 20 }" |
| | | :chartStyle="{ height: '160px', width: '100%' }" |
| | | :lineColors="['#409EFF']" |
| | | /> |
| | | </div> |
| | | |
| | | <div class="metric" :class="{ 'metric-alert': dev.alerts.temperature }"> |
| | | <div class="metric-head"> |
| | | <span>温度(°C)</span> |
| | | <el-tag :type="dev.alerts.temperature ? 'warning' : 'info'" size="small">{{ dev.alerts.temperature ? 'è¶ç' : '20~80' }}</el-tag> |
| | | </div> |
| | | <div class="metric-value">{{ currentValue(dev.series.temperature).toFixed(1) }}</div> |
| | | <Echarts |
| | | :xAxis="[{ type: 'category', data: xAxisLabels }]" |
| | | :yAxis="[{ type: 'value', name: '°C' }]" |
| | | :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.temperature }]" |
| | | :tooltip="{ trigger: 'axis' }" |
| | | :grid="{ left: 40, right: 10, top: 10, bottom: 20 }" |
| | | :chartStyle="{ height: '160px', width: '100%' }" |
| | | :lineColors="['#E6A23C']" |
| | | /> |
| | | </div> |
| | | |
| | | <div class="metric" :class="{ 'metric-alert': dev.alerts.pressure }"> |
| | | <div class="metric-head"> |
| | | <span>åå(MPa)</span> |
| | | <el-tag :type="dev.alerts.pressure ? 'warning' : 'info'" size="small">{{ dev.alerts.pressure ? 'è¶ç' : '0.2~1.5' }}</el-tag> |
| | | </div> |
| | | <div class="metric-value">{{ currentValue(dev.series.pressure).toFixed(2) }}</div> |
| | | <Echarts |
| | | :xAxis="[{ type: 'category', data: xAxisLabels }]" |
| | | :yAxis="[{ type: 'value', name: 'MPa' }]" |
| | | :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.pressure }]" |
| | | :tooltip="{ trigger: 'axis' }" |
| | | :grid="{ left: 40, right: 10, top: 10, bottom: 20 }" |
| | | :chartStyle="{ height: '160px', width: '100%' }" |
| | | :lineColors="['#67C23A']" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue' |
| | | import { ElNotification } from 'element-plus' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | |
| | | defineOptions({ name: 'IoTMonitor' }) |
| | | |
| | | const windowSize = 30 |
| | | const collecting = ref(true) |
| | | const lastUpdated = ref(Date.now()) |
| | | const lastUpdatedDisplay = computed(() => new Date(lastUpdated.value).toLocaleTimeString()) |
| | | |
| | | const xAxisLabels = ref(Array.from({ length: windowSize }, (_, i) => i - (windowSize - 1)).map(n => `${n}s`)) |
| | | |
| | | function makeSeries(fill, decimals = 2) { |
| | | return Array.from({ length: windowSize }, () => Number(fill.toFixed(decimals))) |
| | | } |
| | | |
| | | const devices = reactive([ |
| | | { |
| | | id: 'water-pump', |
| | | name: '注水泵1', |
| | | type: 'ç§»å¨è£
å¤', |
| | | baseline: { vibration: 9 }, |
| | | initial: { temperature: 40, pressure: 0.70 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(9), |
| | | temperature: makeSeries(40, 1), |
| | | pressure: makeSeries(0.7, 2), |
| | | }, |
| | | }, |
| | | { |
| | | id: 'fluid-supply-truck', |
| | | name: '注水泵2', |
| | | type: 'ç§»å¨è£
å¤', |
| | | baseline: { vibration: 7 }, |
| | | initial: { temperature: 30, pressure: 0.60 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(7), |
| | | temperature: makeSeries(30, 1), |
| | | pressure: makeSeries(0.6, 2), |
| | | }, |
| | | }, |
| | | { |
| | | id: 'fracturing-truck', |
| | | name: '注水泵3', |
| | | type: 'ç§»å¨è£
å¤', |
| | | baseline: { vibration: 12 }, |
| | | initial: { temperature: 65, pressure: 1.40 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(12), |
| | | temperature: makeSeries(65, 1), |
| | | pressure: makeSeries(1.4, 2), |
| | | }, |
| | | }, |
| | | { |
| | | id: 'oil-tank-truck', |
| | | name: '注水泵4', |
| | | type: 'ç§»å¨è£
å¤', |
| | | baseline: { vibration: 6 }, |
| | | initial: { temperature: 28, pressure: 0.50 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(6), |
| | | temperature: makeSeries(28, 1), |
| | | pressure: makeSeries(0.5, 2), |
| | | }, |
| | | }, |
| | | ]) |
| | | |
| | | function currentValue(arr) { |
| | | return arr[arr.length - 1] ?? 0 |
| | | } |
| | | |
| | | function pushWindow(arr, val) { |
| | | if (arr.length >= windowSize) arr.shift() |
| | | arr.push(val) |
| | | } |
| | | |
| | | function clamp(val, min, max) { return Math.max(min, Math.min(max, val)) } |
| | | |
| | | function tickDevice(dev) { |
| | | const vibBase = dev.baseline.vibration |
| | | // æ¯å¨ï¼åºçº¿Â±2%éæºæ³¢å¨ï¼5%æ¦ç触å8%~12%å°å³°æ¨¡æåè¦ |
| | | const spike = Math.random() < 0.05 |
| | | const vibNoise = vibBase * (spike ? (1 + (Math.random() * 0.08 + 0.04) * (Math.random() < 0.5 ? -1 : 1)) : (1 + (Math.random() - 0.5) * 0.04)) |
| | | const vibVal = Number(vibNoise.toFixed(2)) |
| | | pushWindow(dev.series.vibration, vibVal) |
| | | |
| | | // 温度ï¼ç¼æ
¢éæºæ¸¸èµ°ï¼å¹¶æ·»å å¶å髿¸©åç§» |
| | | const tPrev = currentValue(dev.series.temperature) |
| | | const tDrift = tPrev + (Math.random() - 0.5) * 0.8 + (Math.random() < 0.02 ? 6 : 0) |
| | | const tVal = Number(clamp(tDrift, 15, 95).toFixed(1)) |
| | | pushWindow(dev.series.temperature, tVal) |
| | | |
| | | // ååï¼å°å¹
æ³¢å¨ï¼å¶åä½å/é«å |
| | | const pPrev = currentValue(dev.series.pressure) |
| | | const pDrift = pPrev + (Math.random() - 0.5) * 0.05 + (Math.random() < 0.02 ? (Math.random() < 0.5 ? -0.3 : 0.3) : 0) |
| | | const pVal = Number(clamp(pDrift, 0.05, 2.0).toFixed(2)) |
| | | pushWindow(dev.series.pressure, pVal) |
| | | |
| | | // è¾¹ç¼è®¡ç®éå¼å¤æ |
| | | const vibDelta = Math.abs(vibVal - vibBase) / vibBase |
| | | const vibAlert = vibDelta > 0.05 |
| | | const tAlert = tVal < 20 || tVal > 80 |
| | | const pAlert = pVal < 0.2 || pVal > 1.5 |
| | | |
| | | const prevHasAlert = dev.hasAlert |
| | | dev.alerts.vibration = vibAlert |
| | | dev.alerts.temperature = tAlert |
| | | dev.alerts.pressure = pAlert |
| | | dev.hasAlert = vibAlert || tAlert || pAlert |
| | | |
| | | if (dev.hasAlert && !prevHasAlert) { |
| | | const reasons = [] |
| | | if (vibAlert) reasons.push(`æ¯å¨å离±5% (å½å ${vibVal} / åºçº¿ ${vibBase})`) |
| | | if (tAlert) reasons.push(`温度è¶ç (å½å ${tVal}°C, ææ 20~80°C) `) |
| | | if (pAlert) reasons.push(`ååè¶ç (å½å ${pVal}MPa, ææ 0.2~1.5MPa) `) |
| | | ElNotification({ |
| | | title: `${dev.name} åè¦`, |
| | | message: reasons.join('ï¼'), |
| | | type: vibAlert ? 'error' : 'warning', |
| | | duration: 5000, |
| | | }) |
| | | } |
| | | } |
| | | |
| | | let timer = null |
| | | function start() { |
| | | if (timer) return |
| | | timer = setInterval(() => { |
| | | if (!collecting.value) return |
| | | devices.forEach(tickDevice) |
| | | lastUpdated.value = Date.now() |
| | | }, 10000) |
| | | } |
| | | |
| | | function stop() { |
| | | if (timer) { |
| | | clearInterval(timer) |
| | | timer = null |
| | | } |
| | | } |
| | | |
| | | function toggleCollecting() { collecting.value = !collecting.value } |
| | | |
| | | function resetAll() { |
| | | devices.forEach(dev => { |
| | | dev.series.vibration = makeSeries(dev.baseline.vibration) |
| | | const t0 = dev.initial?.temperature ?? 45 |
| | | const p0 = dev.initial?.pressure ?? 0.8 |
| | | dev.series.temperature = makeSeries(t0, 1) |
| | | dev.series.pressure = makeSeries(p0, 2) |
| | | dev.alerts.vibration = false |
| | | dev.alerts.temperature = false |
| | | dev.alerts.pressure = false |
| | | dev.hasAlert = false |
| | | }) |
| | | lastUpdated.value = Date.now() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | start() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | stop() |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .iot-monitor { |
| | | .header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 12px; |
| | | .title { font-size: 18px; font-weight: 600; } |
| | | .actions { display: flex; align-items: center; gap: 8px; } |
| | | .ts { color: #909399; font-size: 12px; } |
| | | } |
| | | .rule-alert { margin-bottom: 12px; } |
| | | } |
| | | |
| | | .device-card { |
| | | margin-bottom: 16px; |
| | | transition: border-color 0.2s ease, box-shadow 0.2s ease; |
| | | &.is-alert { border-color: #F56C6C; box-shadow: 0 0 0 2px rgba(245,108,108,0.2) inset; } |
| | | .card-header { |
| | | display: flex; flex-direction: column; gap: 4px; |
| | | .card-title { display: flex; align-items: center; gap: 8px; font-weight: 600; } |
| | | .meta { color: #909399; font-size: 12px; } |
| | | } |
| | | .metrics { |
| | | display: grid; |
| | | grid-template-columns: 1fr; |
| | | gap: 12px; |
| | | } |
| | | } |
| | | |
| | | .metric { |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 6px; |
| | | padding: 8px 8px 0 8px; |
| | | &-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; font-size: 13px; color: #606266; } |
| | | &-value { font-size: 20px; font-weight: 600; margin: 2px 0 6px 0; } |
| | | } |
| | | |
| | | .metric-alert { |
| | | border-color: #F56C6C; |
| | | background: #FFF6F6; |
| | | } |
| | | |
| | | @media (min-width: 1200px) { |
| | | .device-card .metrics { grid-template-columns: 1fr 1fr 1fr; } |
| | | } |
| | | </style> |
| | | |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container iot-monitor"> |
| | | <div class="header"> |
| | | <div class="title">宿¶å·¥åµçæ§ï¼IoTï¼</div> |
| | | <div class="actions"> |
| | | <el-button type="primary" @click="toggleCollecting">{{ collecting ? 'æåéé' : 'å¯å¨éé' }}</el-button> |
| | | <el-button @click="resetAll">éç½®</el-button> |
| | | <span class="ts">䏿¬¡æ´æ°æ¶é´ï¼{{ lastUpdatedDisplay }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- <el-alert--> |
| | | <!-- title="è¾¹ç¼é¢è¦è§åï¼è½´æ¿ç£¨æ-æ¯å¨å¼å离åºçº¿Â±5%触ååè¦ï¼æ¸©åº¦/ååè¶ç触åæé"--> |
| | | <!-- type="info"--> |
| | | <!-- :closable="false"--> |
| | | <!-- show-icon--> |
| | | <!-- class="rule-alert"--> |
| | | <!-- />--> |
| | | |
| | | <el-row :gutter="16"> |
| | | <el-col v-for="dev in devices" :key="dev.id" :span="12"> |
| | | <el-card :class="['device-card', dev.hasAlert ? 'is-alert' : '']"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <div class="card-title"> |
| | | <span class="device-name">{{ dev.name }}</span> |
| | | <el-tag :type="dev.hasAlert ? 'danger' : 'success'" size="small">{{ dev.hasAlert ? 'åè¦' : 'æ£å¸¸' }}</el-tag> |
| | | </div> |
| | | <div class="meta">ç±»åï¼{{ dev.type }}ï½åºçº¿æ¯å¨ï¼{{ dev.baseline.vibration.toFixed(2) }} mm/s</div> |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="metrics"> |
| | | <div class="metric" :class="{ 'metric-alert': dev.alerts.vibration }"> |
| | | <div class="metric-head"> |
| | | <span>æ¯å¨(mm/s)</span> |
| | | <el-tag :type="dev.alerts.vibration ? 'danger' : 'info'" size="small">{{ dev.alerts.vibration ? '±5%è¶ç' : 'åºçº¿Â±5%' }}</el-tag> |
| | | </div> |
| | | <div class="metric-value">{{ currentValue(dev.series.vibration).toFixed(2) }}</div> |
| | | <Echarts |
| | | :xAxis="[{ type: 'category', data: xAxisLabels }]" |
| | | :yAxis="[{ type: 'value', name: 'mm/s' }]" |
| | | :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.vibration }]" |
| | | :tooltip="{ trigger: 'axis' }" |
| | | :grid="{ left: 40, right: 10, top: 10, bottom: 20 }" |
| | | :chartStyle="{ height: '160px', width: '100%' }" |
| | | :lineColors="['#409EFF']" |
| | | /> |
| | | </div> |
| | | |
| | | <div class="metric" :class="{ 'metric-alert': dev.alerts.temperature }"> |
| | | <div class="metric-head"> |
| | | <span>温度(°C)</span> |
| | | <el-tag :type="dev.alerts.temperature ? 'warning' : 'info'" size="small">{{ dev.alerts.temperature ? 'è¶ç' : '20~80' }}</el-tag> |
| | | </div> |
| | | <div class="metric-value">{{ currentValue(dev.series.temperature).toFixed(1) }}</div> |
| | | <Echarts |
| | | :xAxis="[{ type: 'category', data: xAxisLabels }]" |
| | | :yAxis="[{ type: 'value', name: '°C' }]" |
| | | :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.temperature }]" |
| | | :tooltip="{ trigger: 'axis' }" |
| | | :grid="{ left: 40, right: 10, top: 10, bottom: 20 }" |
| | | :chartStyle="{ height: '160px', width: '100%' }" |
| | | :lineColors="['#E6A23C']" |
| | | /> |
| | | </div> |
| | | |
| | | <div class="metric" :class="{ 'metric-alert': dev.alerts.pressure }"> |
| | | <div class="metric-head"> |
| | | <span>åå(MPa)</span> |
| | | <el-tag :type="dev.alerts.pressure ? 'warning' : 'info'" size="small">{{ dev.alerts.pressure ? 'è¶ç' : '0.2~1.5' }}</el-tag> |
| | | </div> |
| | | <div class="metric-value">{{ currentValue(dev.series.pressure).toFixed(2) }}</div> |
| | | <Echarts |
| | | :xAxis="[{ type: 'category', data: xAxisLabels }]" |
| | | :yAxis="[{ type: 'value', name: 'MPa' }]" |
| | | :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.pressure }]" |
| | | :tooltip="{ trigger: 'axis' }" |
| | | :grid="{ left: 40, right: 10, top: 10, bottom: 20 }" |
| | | :chartStyle="{ height: '160px', width: '100%' }" |
| | | :lineColors="['#67C23A']" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue' |
| | | import { ElNotification } from 'element-plus' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | |
| | | defineOptions({ name: 'IoTMonitor' }) |
| | | |
| | | const windowSize = 30 |
| | | const collecting = ref(true) |
| | | const lastUpdated = ref(Date.now()) |
| | | const lastUpdatedDisplay = computed(() => new Date(lastUpdated.value).toLocaleTimeString()) |
| | | |
| | | const xAxisLabels = ref(Array.from({ length: windowSize }, (_, i) => i - (windowSize - 1)).map(n => `${n}s`)) |
| | | |
| | | function makeSeries(fill, decimals = 2) { |
| | | return Array.from({ length: windowSize }, () => Number(fill.toFixed(decimals))) |
| | | } |
| | | |
| | | const devices = reactive([ |
| | | { |
| | | id: 'hydrocyclone-desander', |
| | | name: 'ææµé¤ç å¨', |
| | | type: 'å离设å¤', |
| | | baseline: { vibration: 8 }, |
| | | initial: { temperature: 35, pressure: 0.85 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(8), |
| | | temperature: makeSeries(35, 1), |
| | | pressure: makeSeries(0.85, 2), |
| | | }, |
| | | }, |
| | | { |
| | | id: 'high-pressure-separator', |
| | | name: 'é«ååç¦»å¨æ¬', |
| | | type: 'å离设å¤', |
| | | baseline: { vibration: 6 }, |
| | | initial: { temperature: 45, pressure: 1.20 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(6), |
| | | temperature: makeSeries(45, 1), |
| | | pressure: makeSeries(1.2, 2), |
| | | }, |
| | | }, |
| | | { |
| | | id: 'heating-throttle-pressure', |
| | | name: 'ç»åå¼å çèæµè°å', |
| | | type: 'è°å设å¤', |
| | | baseline: { vibration: 10 }, |
| | | initial: { temperature: 75, pressure: 1.80 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(10), |
| | | temperature: makeSeries(75, 1), |
| | | pressure: makeSeries(1.8, 2), |
| | | }, |
| | | }, |
| | | { |
| | | id: 'three-phase-separator', |
| | | name: 'ä¸ç¸å离å¨', |
| | | type: 'å离设å¤', |
| | | baseline: { vibration: 7 }, |
| | | initial: { temperature: 38, pressure: 0.95 }, |
| | | alerts: { vibration: false, temperature: false, pressure: false }, |
| | | hasAlert: false, |
| | | series: { |
| | | vibration: makeSeries(7), |
| | | temperature: makeSeries(38, 1), |
| | | pressure: makeSeries(0.95, 2), |
| | | }, |
| | | }, |
| | | ]) |
| | | |
| | | function currentValue(arr) { |
| | | return arr[arr.length - 1] ?? 0 |
| | | } |
| | | |
| | | function pushWindow(arr, val) { |
| | | if (arr.length >= windowSize) arr.shift() |
| | | arr.push(val) |
| | | } |
| | | |
| | | function clamp(val, min, max) { return Math.max(min, Math.min(max, val)) } |
| | | |
| | | function tickDevice(dev) { |
| | | const vibBase = dev.baseline.vibration |
| | | // æ¯å¨ï¼åºçº¿Â±2%éæºæ³¢å¨ï¼5%æ¦ç触å8%~12%å°å³°æ¨¡æåè¦ |
| | | const spike = Math.random() < 0.05 |
| | | const vibNoise = vibBase * (spike ? (1 + (Math.random() * 0.08 + 0.04) * (Math.random() < 0.5 ? -1 : 1)) : (1 + (Math.random() - 0.5) * 0.04)) |
| | | const vibVal = Number(vibNoise.toFixed(2)) |
| | | pushWindow(dev.series.vibration, vibVal) |
| | | |
| | | // 温度ï¼ç¼æ
¢éæºæ¸¸èµ°ï¼å¹¶æ·»å å¶å髿¸©åç§» |
| | | const tPrev = currentValue(dev.series.temperature) |
| | | const tDrift = tPrev + (Math.random() - 0.5) * 0.8 + (Math.random() < 0.02 ? 6 : 0) |
| | | const tVal = Number(clamp(tDrift, 15, 95).toFixed(1)) |
| | | pushWindow(dev.series.temperature, tVal) |
| | | |
| | | // ååï¼å°å¹
æ³¢å¨ï¼å¶åä½å/é«å |
| | | const pPrev = currentValue(dev.series.pressure) |
| | | const pDrift = pPrev + (Math.random() - 0.5) * 0.05 + (Math.random() < 0.02 ? (Math.random() < 0.5 ? -0.3 : 0.3) : 0) |
| | | const pVal = Number(clamp(pDrift, 0.05, 2.0).toFixed(2)) |
| | | pushWindow(dev.series.pressure, pVal) |
| | | |
| | | // è¾¹ç¼è®¡ç®éå¼å¤æ |
| | | const vibDelta = Math.abs(vibVal - vibBase) / vibBase |
| | | const vibAlert = vibDelta > 0.05 |
| | | const tAlert = tVal < 20 || tVal > 80 |
| | | const pAlert = pVal < 0.2 || pVal > 1.5 |
| | | |
| | | const prevHasAlert = dev.hasAlert |
| | | dev.alerts.vibration = vibAlert |
| | | dev.alerts.temperature = tAlert |
| | | dev.alerts.pressure = pAlert |
| | | dev.hasAlert = vibAlert || tAlert || pAlert |
| | | |
| | | if (dev.hasAlert && !prevHasAlert) { |
| | | const reasons = [] |
| | | if (vibAlert) reasons.push(`æ¯å¨å离±5% (å½å ${vibVal} / åºçº¿ ${vibBase})`) |
| | | if (tAlert) reasons.push(`温度è¶ç (å½å ${tVal}°C, ææ 20~80°C) `) |
| | | if (pAlert) reasons.push(`ååè¶ç (å½å ${pVal}MPa, ææ 0.2~1.5MPa) `) |
| | | ElNotification({ |
| | | title: `${dev.name} åè¦`, |
| | | message: reasons.join('ï¼'), |
| | | type: vibAlert ? 'error' : 'warning', |
| | | duration: 5000, |
| | | }) |
| | | } |
| | | } |
| | | |
| | | let timer = null |
| | | function start() { |
| | | if (timer) return |
| | | timer = setInterval(() => { |
| | | if (!collecting.value) return |
| | | devices.forEach(tickDevice) |
| | | lastUpdated.value = Date.now() |
| | | }, 10000) |
| | | } |
| | | |
| | | function stop() { |
| | | if (timer) { |
| | | clearInterval(timer) |
| | | timer = null |
| | | } |
| | | } |
| | | |
| | | function toggleCollecting() { collecting.value = !collecting.value } |
| | | |
| | | function resetAll() { |
| | | devices.forEach(dev => { |
| | | dev.series.vibration = makeSeries(dev.baseline.vibration) |
| | | const t0 = dev.initial?.temperature ?? 45 |
| | | const p0 = dev.initial?.pressure ?? 0.8 |
| | | dev.series.temperature = makeSeries(t0, 1) |
| | | dev.series.pressure = makeSeries(p0, 2) |
| | | dev.alerts.vibration = false |
| | | dev.alerts.temperature = false |
| | | dev.alerts.pressure = false |
| | | dev.hasAlert = false |
| | | }) |
| | | lastUpdated.value = Date.now() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | start() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | stop() |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .iot-monitor { |
| | | .header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 12px; |
| | | .title { font-size: 18px; font-weight: 600; } |
| | | .actions { display: flex; align-items: center; gap: 8px; } |
| | | .ts { color: #909399; font-size: 12px; } |
| | | } |
| | | .rule-alert { margin-bottom: 12px; } |
| | | } |
| | | |
| | | .device-card { |
| | | margin-bottom: 16px; |
| | | transition: border-color 0.2s ease, box-shadow 0.2s ease; |
| | | &.is-alert { border-color: #F56C6C; box-shadow: 0 0 0 2px rgba(245,108,108,0.2) inset; } |
| | | .card-header { |
| | | display: flex; flex-direction: column; gap: 4px; |
| | | .card-title { display: flex; align-items: center; gap: 8px; font-weight: 600; } |
| | | .meta { color: #909399; font-size: 12px; } |
| | | } |
| | | .metrics { |
| | | display: grid; |
| | | grid-template-columns: 1fr; |
| | | gap: 12px; |
| | | } |
| | | } |
| | | |
| | | .metric { |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 6px; |
| | | padding: 8px 8px 0 8px; |
| | | &-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; font-size: 13px; color: #606266; } |
| | | &-value { font-size: 20px; font-weight: 600; margin: 2px 0 6px 0; } |
| | | } |
| | | |
| | | .metric-alert { |
| | | border-color: #F56C6C; |
| | | background: #FFF6F6; |
| | | } |
| | | |
| | | @media (min-width: 1200px) { |
| | | .device-card .metrics { grid-template-columns: 1fr 1fr 1fr; } |
| | | } |
| | | </style> |
| | | |
| | | |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·" prop="deviceModel"> |
| | | <el-input v-model="form.deviceModel" placeholder="请è¾å
¥è§æ ¼åå·" /> |
| | | <el-input v-model="form.deviceModel" :disabled="(form.deviceModel != null && operationType === 'edit')" placeholder="请è¾å
¥è§æ ¼åå·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | name: "设å¤å°è´¦è¡¨å", |
| | | }); |
| | | const formRef = ref(null); |
| | | const operationType = ref(''); |
| | | const formRules = { |
| | | deviceName: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | deviceModel: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | |
| | | }); |
| | | |
| | | const loadForm = async (id) => { |
| | | if (id) { |
| | | operationType.value = 'edit' |
| | | } |
| | | const { code, data } = await getLedgerById(id); |
| | | if (code == 200) { |
| | | form.deviceName = data.deviceName; |
| | |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | :isShowSummary="true" |
| | | :summaryMethod="summaryMethod" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | |
| | | import dayjs from "dayjs"; |
| | | import QRCode from "qrcode"; |
| | | import { ref } from "vue"; |
| | | import { summarizeTable } from "@/utils/summarizeTable"; |
| | | import {Search} from "@element-plus/icons-vue"; |
| | | |
| | | defineOptions({ |
| | | name: "设å¤å°è´¦", |
| | |
| | | } = usePaginationApi( |
| | | getLedgerPage, |
| | | { |
| | | searchText: undefined, |
| | | deviceName: undefined, |
| | | deviceModel: undefined, |
| | | supplierName: undefined, |
| | | unit: undefined, |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | }, |
| | | [ |
| | | { |
| | |
| | | pagination.pageSize = limit; |
| | | onCurrentChange(page); |
| | | }; |
| | | |
| | | // åè®¡æ¹æ³ |
| | | const summaryMethod = (param) => { |
| | | return summarizeTable( |
| | | param, |
| | | ['number', 'taxIncludingPriceTotal', 'unTaxIncludingPriceTotal', 'taxIncludingPriceUnit'], |
| | | { |
| | | number: { noDecimal: true }, |
| | | taxIncludingPriceTotal: { decimalPlaces: 2 }, |
| | | unTaxIncludingPriceTotal: { decimalPlaces: 2 } |
| | | } |
| | | ); |
| | | }; |
| | | |
| | | const deleteRow = (id) => { |
| | | ElMessageBox.confirm("æ¤æä½å°æ°¸ä¹
å é¤è¯¥æä»¶, æ¯å¦ç»§ç»?", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éä»¶ææï¼" prop="remark"> |
| | | <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload |
| | | :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError" |
| | | :on-success="handleUploadSuccess" :on-remove="handleRemove"> |
| | | <el-button type="primary" v-if="operationType !== 'view'">ä¸ä¼ </el-button> |
| | | <template #tip v-if="operationType !== 'view'"> |
| | | <div class="el-upload__tip"> |
| | | æä»¶æ ¼å¼æ¯æ |
| | | docï¼docxï¼xlsï¼xlsxï¼pptï¼pptxï¼pdfï¼txtï¼xmlï¼jpgï¼jpegï¼pngï¼gifï¼bmpï¼rarï¼zipï¼7z |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- <el-row :gutter="30">--> |
| | | <!-- <el-col :span="24">--> |
| | | <!-- <el-form-item label="éä»¶ææï¼" prop="remark">--> |
| | | <!-- <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload--> |
| | | <!-- :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError"--> |
| | | <!-- :on-success="handleUploadSuccess" :on-remove="handleRemove">--> |
| | | <!-- <el-button type="primary" v-if="operationType !== 'view'">ä¸ä¼ </el-button>--> |
| | | <!-- <template #tip v-if="operationType !== 'view'">--> |
| | | <!-- <div class="el-upload__tip">--> |
| | | <!-- æä»¶æ ¼å¼æ¯æ--> |
| | | <!-- docï¼docxï¼xlsï¼xlsxï¼pptï¼pptxï¼pdfï¼txtï¼xmlï¼jpgï¼jpegï¼pngï¼gifï¼bmpï¼rarï¼zipï¼7z--> |
| | | <!-- </div>--> |
| | | <!-- </template>--> |
| | | <!-- </el-upload>--> |
| | | <!-- </el-form-item>--> |
| | | <!-- </el-col>--> |
| | | <!-- </el-row>--> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éä»¶ææï¼" prop="remark"> |
| | | <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload |
| | | :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError" |
| | | :on-success="handleUploadSuccess" :on-remove="handleRemove"> |
| | | <el-button type="primary" v-if="operationType !== 'view'">ä¸ä¼ </el-button> |
| | | <template #tip v-if="operationType !== 'view'"> |
| | | <div class="el-upload__tip"> |
| | | æä»¶æ ¼å¼æ¯æ |
| | | docï¼docxï¼xlsï¼xlsxï¼pptï¼pptxï¼pdfï¼txtï¼xmlï¼jpgï¼jpegï¼pngï¼gifï¼bmpï¼rarï¼zipï¼7z |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- <el-row :gutter="30">--> |
| | | <!-- <el-col :span="24">--> |
| | | <!-- <el-form-item label="éä»¶ææï¼" prop="remark">--> |
| | | <!-- <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload--> |
| | | <!-- :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError"--> |
| | | <!-- :on-success="handleUploadSuccess" :on-remove="handleRemove">--> |
| | | <!-- <el-button type="primary" v-if="operationType !== 'view'">ä¸ä¼ </el-button>--> |
| | | <!-- <template #tip v-if="operationType !== 'view'">--> |
| | | <!-- <div class="el-upload__tip">--> |
| | | <!-- æä»¶æ ¼å¼æ¯æ--> |
| | | <!-- docï¼docxï¼xlsï¼xlsxï¼pptï¼pptxï¼pdfï¼txtï¼xmlï¼jpgï¼jpegï¼pngï¼gifï¼bmpï¼rarï¼zipï¼7z--> |
| | | <!-- </div>--> |
| | | <!-- </template>--> |
| | | <!-- </el-upload>--> |
| | | <!-- </el-form-item>--> |
| | | <!-- </el-col>--> |
| | | <!-- </el-row>--> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | title="ä¸ä¼ éä»¶" |
| | | width="50%" |
| | | @close="closeDia" |
| | | > |
| | | <div style="margin-bottom: 10px;text-align: right"> |
| | | <el-upload |
| | | v-model:file-list="fileList" |
| | | class="upload-demo" |
| | | :action="uploadUrl" |
| | | :on-success="handleUploadSuccess" |
| | | :on-error="handleUploadError" |
| | | name="file" |
| | | :show-file-list="false" |
| | | :headers="headers" |
| | | style="display: inline;margin-right: 10px" |
| | | > |
| | | <el-button type="primary">ä¸ä¼ éä»¶</el-button> |
| | | </el-upload> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | height="500" |
| | | > |
| | | </PIMTable> |
| | | <pagination |
| | | style="margin: 10px 0" |
| | | v-show="total > 0" |
| | | @pagination="paginationSearch" |
| | | :total="total" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | /> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <filePreview ref="filePreviewRef" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import {getToken} from "@/utils/auth.js"; |
| | | import filePreview from '@/components/filePreview/index.vue' |
| | | import { |
| | | fileAdd, |
| | | fileDel, |
| | | fileListPage |
| | | } from "@/api/financialManagement/revenueManagement.js"; |
| | | import Pagination from "@/components/PIMTable/Pagination.vue"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const currentId = ref('') |
| | | const selectedRows = ref([]); |
| | | const filePreviewRef = ref() |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "æä»¶åç§°", |
| | | prop: "name", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | operation: [ |
| | | { |
| | | name: "ä¸è½½", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | downLoadFile(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "é¢è§", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | lookFile(row); |
| | | }, |
| | | } |
| | | ], |
| | | }, |
| | | ]); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const tableData = ref([]); |
| | | const fileList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const accountType = ref('') |
| | | const headers = ref({ |
| | | Authorization: "Bearer " + getToken(), |
| | | }); |
| | | const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); // ä¸ä¼ çå¾çæå¡å¨å°å |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (row,type) => { |
| | | accountType.value = type; |
| | | dialogFormVisible.value = true; |
| | | currentId.value = row.id; |
| | | getList() |
| | | } |
| | | const paginationSearch = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | fileListPage({accountId: currentId.value,accountType:accountType.value, ...page}).then(res => { |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | }) |
| | | } |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | // ä¸ä¼ æåå¤ç |
| | | function handleUploadSuccess(res, file) { |
| | | // 妿ä¸ä¼ æå |
| | | if (res.code == 200) { |
| | | const fileRow = {} |
| | | fileRow.name = res.data.originalName |
| | | fileRow.url = res.data.tempPath |
| | | uploadFile(fileRow) |
| | | } else { |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | } |
| | | } |
| | | function uploadFile(file) { |
| | | file.accountId = currentId.value; |
| | | file.accountType = accountType.value; |
| | | fileAdd(file).then(res => { |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); |
| | | getList() |
| | | }) |
| | | } |
| | | // ä¸ä¼ 失败å¤ç |
| | | function handleUploadError() { |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | } |
| | | // ä¸è½½éä»¶ |
| | | const downLoadFile = (row) => { |
| | | proxy.$download.name(row.url); |
| | | } |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | fileDel(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }).catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // é¢è§éä»¶ |
| | | const lookFile = (row) => { |
| | | filePreviewRef.value.open(row.url) |
| | | } |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
| | |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | <calibration-dia ref="calibrationDia" @close="handleQuery"></calibration-dia> |
| | | <files-dia ref="filesDia"></files-dia> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | measuringInstrumentDelete, |
| | | measuringInstrumentListPage |
| | | } from "@/api/equipmentManagement/measurementEquipment.js"; |
| | | import FilesDia from "./filesDia.vue"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore() |
| | | |
| | |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | width: '130', |
| | | fixed: 'right', |
| | | operation: [ |
| | | { |
| | |
| | | name: "éä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openCalibrationDia("add", row); |
| | | openFilesFormDia(row); |
| | | }, |
| | | }, |
| | | ], |
| | |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const filesDia = ref() |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | |
| | | }); |
| | | const selectedRows = ref([]); |
| | | |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = (row) => { |
| | | console.log(row) |
| | | nextTick(() => { |
| | | filesDia.value?.openDialog( row,'计éå¨å
·å°è´¦') |
| | | }) |
| | | }; |
| | | |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | |
| | | <template> |
| | | <el-drawer v-model="visible" :title="modalOptions.title" direction="ltr"> |
| | | <el-dialog v-model="visible" :title="modalOptions.title" direction="ltr"> |
| | | <MaintainForm ref="maintainFormRef" /> |
| | | <template #footer> |
| | | <el-button type="primary" @click="sendForm" :loading="loading"> |
| | |
| | | </el-button> |
| | | <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button> |
| | | </template> |
| | | </el-drawer> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | import MaintainModal from "./Modal/MaintainModal.vue"; |
| | | import {Search} from "@element-plus/icons-vue"; |
| | | |
| | | defineOptions({ |
| | | name: "è®¾å¤æ¥ä¿®", |
| | |
| | | } = usePaginationApi( |
| | | getRepairPage, |
| | | { |
| | | searchText: undefined, |
| | | deviceName: undefined, |
| | | deviceModel: undefined, |
| | | remark: undefined, |
| | | maintenanceName: undefined, |
| | | repairTimeStr: undefined, |
| | | maintenanceTimeStr: undefined, |
| | | }, |
| | | [ |
| | | { |
| | |
| | | <template> |
| | | <el-drawer v-model="visible" :title="modalOptions.title" direction="ltr"> |
| | | <el-dialog v-model="visible" :title="modalOptions.title" direction="ltr"> |
| | | <MaintenanceForm ref="maintenanceFormRef" /> |
| | | <template #footer> |
| | | <el-button type="primary" @click="sendForm" :loading="loading"> |
| | |
| | | </el-button> |
| | | <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button> |
| | | </template> |
| | | </el-drawer> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | |
| | | import MaintenanceModal from "./Modal/MaintenanceModal.vue"; |
| | | import dayjs from "dayjs"; |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | | import {Search} from "@element-plus/icons-vue"; |
| | | |
| | | defineOptions({ |
| | | name: "设å¤ä¿å
»", |
| | |
| | | getTableData, |
| | | resetFilters, |
| | | onCurrentChange, |
| | | } = usePaginationApi(getUpkeepPage, {}, [ |
| | | } = usePaginationApi(getUpkeepPage, { |
| | | deviceName: undefined, |
| | | maintenancePlanTime: undefined, |
| | | maintenanceActuallyTime: undefined, |
| | | maintenanceActuallyName: undefined, |
| | | }, [ |
| | | { |
| | | label: "设å¤åç§°", |
| | | align: "center", |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="å½å
¥æ¥æ:"> |
| | | <el-form-item label="æ¯åºæ¥æ:"> |
| | | <el-date-picker v-model="filters.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | placeholder="è¯·éæ©" clearable @change="changeDaterange" /> |
| | | </el-form-item> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="å½å
¥æ¥æ:"> |
| | | <el-form-item label="æ¶å
¥æ¥æ:"> |
| | | <el-date-picker v-model="filters.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | placeholder="è¯·éæ©" clearable @change="changeDaterange" /> |
| | | </el-form-item> |
| | |
| | | <div class="main-panel"> |
| | | <div style="display: flex;justify-content: space-between;"> |
| | | <div class="section-title">åºæ¶åºä»ç»è®¡</div> |
| | | <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable"> |
| | | <el-radio-button label="æå¨" :value="1" /> |
| | | <el-radio-button label="ææ" :value="2" /> |
| | | <el-radio-button label="æå£åº¦" :value="3" /> |
| | | </el-radio-group> |
| | | <!-- <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable">--> |
| | | <!-- <el-radio-button label="æå¨" :value="1" />--> |
| | | <!-- <el-radio-button label="ææ" :value="2" />--> |
| | | <!-- <el-radio-button label="æå£åº¦" :value="3" />--> |
| | | <!-- </el-radio-group>--> |
| | | </div> |
| | | <Echarts ref="chart" |
| | | :color="barColors2" |
| | |
| | | |
| | | <!-- åºé¨æ¨ªå两æ --> |
| | | <div class="dashboard-row"> |
| | | <div class="main-panel"> |
| | | <div class="section-title">è´¨éç»è®¡</div> |
| | | <div class="quality-cards"> |
| | | <div class="quality-card one">åææå·²æ£æµæ° <span>{{qualityStatisticsObject.supplierNum}}ä»¶</span></div> |
| | | <div class="quality-card two">è¿ç¨æ£éªæ°é <span>{{qualityStatisticsObject.processNum}}ä»¶</span></div> |
| | | <div class="quality-card three">åºåå·²æ£æ°é <span>{{qualityStatisticsObject.factoryNum}}ä»¶</span></div> |
| | | </div> |
| | | <Echarts ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="barLegend" |
| | | :series="barSeries1" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | style="height: 260px"></Echarts> |
| | | </div> |
| | | <!-- <div class="main-panel">--> |
| | | <!-- <div class="section-title">è´¨éç»è®¡</div>--> |
| | | <!-- <div class="quality-cards">--> |
| | | <!-- <div class="quality-card one">åææå·²æ£æµæ° <span>{{qualityStatisticsObject.supplierNum}}ä»¶</span></div>--> |
| | | <!-- <div class="quality-card two">è¿ç¨æ£éªæ°é <span>{{qualityStatisticsObject.processNum}}ä»¶</span></div>--> |
| | | <!-- <div class="quality-card three">åºåå·²æ£æ°é <span>{{qualityStatisticsObject.factoryNum}}ä»¶</span></div>--> |
| | | <!-- </div>--> |
| | | <!-- <Echarts ref="chart"--> |
| | | <!-- :chartStyle="chartStyle"--> |
| | | <!-- :grid="grid"--> |
| | | <!-- :legend="barLegend"--> |
| | | <!-- :series="barSeries1"--> |
| | | <!-- :tooltip="tooltip"--> |
| | | <!-- :xAxis="xAxis1"--> |
| | | <!-- :yAxis="yAxis1"--> |
| | | <!-- style="height: 260px"></Echarts>--> |
| | | <!-- </div>--> |
| | | <div class="main-panel"> |
| | | <div class="section-title">忬¾ä¸å¼ç¥¨åæ</div> |
| | | <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries" |
| | |
| | | color: #666; |
| | | list-style: none; |
| | | padding: 0; |
| | | height: 190px; |
| | | overflow-y: auto; |
| | | width: 460px; |
| | | } |
| | | .line { |
| | | position: relative; |
| | | width: 250px; |
| | | width: 230px; |
| | | } |
| | | .line::after { |
| | | content: ''; |
| | | position: absolute; |
| | | right: 12px; |
| | | right: 2px; |
| | | top: 0; |
| | | bottom: 0; |
| | | width: 1px; |
| | |
| | | style="width: 100%" |
| | | :summary-method="summarizeMainTable" |
| | | height="calc(100vh - 18.5em)" |
| | | stripe |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column |
| | | label="åºåºæ¥æ" |
| | | prop="createTime" |
| | | min-width="250" |
| | | min-width="130" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | |
| | | <el-table-column |
| | | label="å«ç¨åä»·(å
)" |
| | | prop="taxInclusiveUnitPrice" |
| | | width="100" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="å«ç¨æ»ä»·(å
)" |
| | | prop="taxInclusiveTotalPrice" |
| | | width="100" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | |
| | | <div class="table_list"> |
| | | <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" :row-key="row => row.id" show-summary style="width: 100%" |
| | | :summary-method="summarizeMainTable" height="calc(100vh - 18.5em)"> |
| | | :summary-method="summarizeMainTable" height="calc(100vh - 18.5em)" stripe> |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="å
¥åºæ¶é´" prop="createTime" width="100" show-overflow-tooltip /> |
| | |
| | | <div class="table_list"> |
| | | <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" :row-key="row => row.id" show-summary style="width: 100%" |
| | | :summary-method="summarizeMainTable" height="calc(100vh - 18.5em)"> |
| | | :summary-method="summarizeMainTable" height="calc(100vh - 18.5em)" stripe> |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="å
¥åºæ¶é´" prop="createTime" width="100" show-overflow-tooltip /> |
| | |
| | | border |
| | | v-loading="loadingProducts" |
| | | @selection-change="handleSelectionChange" |
| | | stripe |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column |
| | |
| | | <div class="table_list"> |
| | | <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" :row-key="row => row.id" show-summary style="width: 100%" |
| | | :summary-method="summarizeMainTable" height="calc(100vh - 18.5em)"> |
| | | :summary-method="summarizeMainTable" height="calc(100vh - 18.5em)" stripe> |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="åºåæ¥æ" prop="createTime" width="100" show-overflow-tooltip /> |
| | |
| | | @selection-change="handleSelectionChange" |
| | | style="width: 100%" |
| | | height="calc(100vh - 280px)" |
| | | stripe |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | |
| | | const { proxy } = getCurrentInstance()
|
| | |
|
| | | const loginForm = ref({
|
| | | username: "admin",
|
| | | password: "admin123",
|
| | | username: "",
|
| | | password: "",
|
| | | rememberMe: false,
|
| | | currentFatoryId:'',
|
| | | })
|
| | |
| | | <style lang='scss' scoped>
|
| | | .login {
|
| | | height: 100%;
|
| | | background-image: url("../assets/indexViews/JZYJView.png");
|
| | | background-image: url("../assets/images/login-background.png");
|
| | | background-size: cover;
|
| | | position: relative;
|
| | | }
|
| | |
| | | <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
| | | </el-row>
|
| | |
|
| | | <el-table v-loading="loading" :data="jobList" @selection-change="handleSelectionChange">
|
| | | <el-table v-loading="loading" :data="jobList" @selection-change="handleSelectionChange" stripe>
|
| | | <el-table-column type="selection" width="55" align="center" />
|
| | | <el-table-column label="ä»»å¡ç¼å·" width="100" align="center" prop="jobId" />
|
| | | <el-table-column label="ä»»å¡åç§°" align="center" prop="jobName" :show-overflow-tooltip="true" />
|
| | |
| | | <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
| | | </el-row>
|
| | |
|
| | | <el-table v-loading="loading" :data="jobLogList" @selection-change="handleSelectionChange">
|
| | | <el-table v-loading="loading" :data="jobLogList" @selection-change="handleSelectionChange" stripe>
|
| | | <el-table-column type="selection" width="55" align="center" />
|
| | | <el-table-column label="æ¥å¿ç¼å·" width="80" align="center" prop="jobLogId" />
|
| | | <el-table-column label="ä»»å¡åç§°" align="center" prop="jobName" :show-overflow-tooltip="true" />
|
| | |
| | | <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
| | | </el-row>
|
| | |
|
| | | <el-table ref="logininforRef" v-loading="loading" :data="logininforList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange">
|
| | | <el-table ref="logininforRef" v-loading="loading" :data="logininforList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange" stripe>
|
| | | <el-table-column type="selection" width="55" align="center" />
|
| | | <el-table-column label="访é®ç¼å·" align="center" prop="infoId" />
|
| | | <el-table-column label="ç¨æ·åç§°" align="center" prop="userName" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']" />
|
| | |
| | | v-loading="loading"
|
| | | :data="onlineList.slice((pageNum - 1) * pageSize, pageNum * pageSize)"
|
| | | style="width: 100%;"
|
| | | stripe
|
| | | >
|
| | | <el-table-column label="åºå·" width="50" type="index" align="center">
|
| | | <template #default="scope">
|
| | |
| | | <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
| | | </el-row>
|
| | |
|
| | | <el-table ref="operlogRef" v-loading="loading" :data="operlogList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange">
|
| | | <el-table ref="operlogRef" v-loading="loading" :data="operlogList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange" stripe>
|
| | | <el-table-column type="selection" width="50" align="center" />
|
| | | <el-table-column label="æ¥å¿ç¼å·" align="center" prop="operId" />
|
| | | <el-table-column label="ç³»ç»æ¨¡å" align="center" prop="title" :show-overflow-tooltip="true" />
|
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | title="ä¸ä¼ éä»¶" |
| | | width="50%" |
| | | @close="closeDia" |
| | | > |
| | | <div style="margin-bottom: 10px;text-align: right"> |
| | | <el-upload |
| | | v-model:file-list="fileList" |
| | | class="upload-demo" |
| | | :action="uploadUrl" |
| | | :on-success="handleUploadSuccess" |
| | | :on-error="handleUploadError" |
| | | name="file" |
| | | :show-file-list="false" |
| | | :headers="headers" |
| | | style="display: inline;margin-right: 10px" |
| | | > |
| | | <el-button type="primary">ä¸ä¼ éä»¶</el-button> |
| | | </el-upload> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | height="500" |
| | | > |
| | | </PIMTable> |
| | | <pagination |
| | | style="margin: 10px 0" |
| | | v-show="total > 0" |
| | | @pagination="paginationSearch" |
| | | :total="total" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | /> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <filePreview ref="filePreviewRef" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import {getToken} from "@/utils/auth.js"; |
| | | import filePreview from '@/components/filePreview/index.vue' |
| | | import { |
| | | fileAdd, |
| | | fileDel, |
| | | fileListPage |
| | | } from "@/api/financialManagement/revenueManagement.js"; |
| | | import Pagination from "@/components/PIMTable/Pagination.vue"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const currentId = ref('') |
| | | const selectedRows = ref([]); |
| | | const filePreviewRef = ref() |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "æä»¶åç§°", |
| | | prop: "name", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | operation: [ |
| | | { |
| | | name: "ä¸è½½", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | downLoadFile(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "é¢è§", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | lookFile(row); |
| | | }, |
| | | } |
| | | ], |
| | | }, |
| | | ]); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const tableData = ref([]); |
| | | const fileList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const accountType = ref('') |
| | | const headers = ref({ |
| | | Authorization: "Bearer " + getToken(), |
| | | }); |
| | | const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); // ä¸ä¼ çå¾çæå¡å¨å°å |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (row,type) => { |
| | | accountType.value = type; |
| | | dialogFormVisible.value = true; |
| | | currentId.value = row.id; |
| | | getList() |
| | | } |
| | | const paginationSearch = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | fileListPage({accountId: currentId.value,accountType:accountType.value, ...page}).then(res => { |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | }) |
| | | } |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | // ä¸ä¼ æåå¤ç |
| | | function handleUploadSuccess(res, file) { |
| | | // 妿ä¸ä¼ æå |
| | | if (res.code == 200) { |
| | | const fileRow = {} |
| | | fileRow.name = res.data.originalName |
| | | fileRow.url = res.data.tempPath |
| | | uploadFile(fileRow) |
| | | } else { |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | } |
| | | } |
| | | function uploadFile(file) { |
| | | file.accountId = currentId.value; |
| | | file.accountType = accountType.value; |
| | | fileAdd(file).then(res => { |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); |
| | | getList() |
| | | }) |
| | | } |
| | | // ä¸ä¼ 失败å¤ç |
| | | function handleUploadError() { |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | } |
| | | // ä¸è½½éä»¶ |
| | | const downLoadFile = (row) => { |
| | | proxy.$download.name(row.url); |
| | | } |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | fileDel(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }).catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // é¢è§éä»¶ |
| | | const lookFile = (row) => { |
| | | filePreviewRef.value.open(row.url) |
| | | } |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
| | |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">å§åï¼</span> |
| | | <el-input |
| | | v-model="searchForm.staffName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å§åæç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span style="margin-left: 10px" class="search_title">ååç»ææ¥æï¼</span> |
| | | <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | placeholder="è¯·éæ©" clearable @change="changeDaterange" /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button |
| | | > |
| | | <el-input v-model="searchForm.staffName" style="width: 240px" placeholder="请è¾å
¥å§åæç´¢" @change="handleQuery" |
| | | clearable :prefix-icon="Search" /> |
| | | <span style="margin-left: 10px" class="search_title">ååç»ææ¥æï¼</span> |
| | | <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | placeholder="è¯·éæ©" clearable @change="changeDaterange" /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> |
| | | </div> |
| | | <div> |
| | | <!-- <el-button type="primary" @click="openForm('add')">æ°å¢å
¥è</el-button>--> |
| | | <!-- <el-button type="info" @click="handleImport">导å
¥</el-button>--> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <!-- <el-button type="danger" plain @click="handleDelete">å é¤</el-button>--> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true" |
| | | @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination" |
| | | :total="page.total"></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | |
| | | <!-- åå导å
¥å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | :title="upload.title" |
| | | v-model="upload.open" |
| | | width="400px" |
| | | append-to-body |
| | | > |
| | | <el-upload |
| | | ref="uploadRef" |
| | | :limit="1" |
| | | accept=".xlsx, .xls" |
| | | :headers="upload.headers" |
| | | :action="upload.url + '?updateSupport=' + upload.updateSupport" |
| | | :disabled="upload.isUploading" |
| | | :on-progress="handleFileUploadProgress" |
| | | :on-success="handleFileSuccess" |
| | | :auto-upload="false" |
| | | drag |
| | | > |
| | | <el-icon class="el-icon--upload"><upload-filled /></el-icon> |
| | | <div class="el-upload__text">å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em></div> |
| | | <template #tip> |
| | | <div class="el-upload__tip text-center"> |
| | | <span>ä»
å
许导å
¥xlsãxlsxæ ¼å¼æä»¶ã</span> |
| | | <!-- <el-link |
| | | type="primary" |
| | | :underline="false" |
| | | style="font-size: 12px; vertical-align: baseline" |
| | | @click="importTemplate" |
| | | >ä¸è½½æ¨¡æ¿</el-link |
| | | > --> |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitFileForm">ç¡® å®</el-button> |
| | | <el-button @click="upload.open = false">å æ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <files-dia ref="filesDia"></files-dia> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref} from "vue"; |
| | | import { onMounted, ref } from "vue"; |
| | | import FormDia from "@/views/personnelManagement/contractManagement/components/formDia.vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import {staffOnJobListPage} from "@/api/personnelManagement/employeeRecord.js"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/employeeRecord.js"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | import { getToken } from "@/utils/auth.js"; |
| | | import FilesDia from "./filesDia.vue"; |
| | | const data = reactive({ |
| | | searchForm: { |
| | | staffName: "", |
| | |
| | | { |
| | | label: "å®¶åºä½å", |
| | | prop: "adress", |
| | | width:200 |
| | | width: 200 |
| | | }, |
| | | { |
| | | label: "第ä¸å¦å", |
| | |
| | | { |
| | | label: "ä¸ä¸", |
| | | prop: "profession", |
| | | width:100 |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "身份è¯å·", |
| | | prop: "identityCard", |
| | | width:200 |
| | | width: 200 |
| | | }, |
| | | { |
| | | label: "å¹´é¾", |
| | |
| | | { |
| | | label: "èç³»çµè¯", |
| | | prop: "phone", |
| | | width:150 |
| | | width: 150 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人", |
| | |
| | | { |
| | | label: "ç´§æ¥è系人çµè¯", |
| | | prop: "emergencyContactPhone", |
| | | width:150 |
| | | width: 150 |
| | | }, |
| | | { |
| | | label: "ååå¹´é", |
| | |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | width: 120, |
| | | operation: [ |
| | | { |
| | | name: "详æ
", |
| | |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openFilesFormDia(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const filesDia = ref() |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | |
| | | searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | } |
| | | getList(); |
| | | }; |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = (row) => { |
| | | console.log(row) |
| | | nextTick(() => { |
| | | filesDia.value?.openDialog( row,'åå') |
| | | }) |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/staff/staffOnJob/export", {}, "åå管ç.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | .then(() => { |
| | | proxy.download("/staff/staffOnJob/export", {}, "åå管ç.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | const upload = reactive({ |
| | | // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼åå导å
¥ï¼ |
| | | open: false, |
| | | // å¼¹åºå±æ é¢ï¼åå导å
¥ï¼ |
| | | title: "", |
| | | // æ¯å¦ç¦ç¨ä¸ä¼ |
| | | isUploading: false, |
| | | // æ¯å¦æ´æ°å·²ç»åå¨çç¨æ·æ°æ® |
| | | updateSupport: 1, |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/staff/staffOnJob/import", |
| | | }); |
| | | /** 导å
¥æé®æä½ */ |
| | | function handleImport() { |
| | | upload.title = "åå导å
¥"; |
| | | upload.open = true; |
| | | } |
| | | /** æäº¤ä¸ä¼ æä»¶ */ |
| | | function submitFileForm() { |
| | | console.log(upload.url + '?updateSupport=' + upload.updateSupport) |
| | | proxy.$refs["uploadRef"].submit(); |
| | | } |
| | | /**æä»¶ä¸ä¼ ä¸å¤ç */ |
| | | const handleFileUploadProgress = (event, file, fileList) => { |
| | | upload.isUploading = true; |
| | | }; |
| | | /** æä»¶ä¸ä¼ æåå¤ç */ |
| | | const handleFileSuccess = (response, file, fileList) => { |
| | | upload.open = false; |
| | | upload.isUploading = false; |
| | | proxy.$refs["uploadRef"].handleRemove(file); |
| | | getList(); |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¯ç¨æï¼æï¼ï¼" prop="probationPeriod"> |
| | | <el-input-number v-model="form.probationPeriod" :precision="0" :step="1" min="0" style="width: 100%"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <!-- <el-col :span="12"> |
| | | <el-form-item label="å
¥èæ¥æï¼" prop="entryDate"> |
| | | <el-date-picker |
| | | v-model="form.entryDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> --> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {ref, reactive, toRefs} from "vue"; |
| | | import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | |
| | | contractStartTime: "", |
| | | contractEndTime: "", |
| | | staffState: "", |
| | | probationPeriod: 3, // é»è®¤è¯ç¨æ3个æ |
| | | }, |
| | | rules: { |
| | | staffNo: [{ required: true, message: "请è¾å
¥", trigger: "blur" },], |
| | |
| | | width:150 |
| | | }, |
| | | { |
| | | label: "ååå¹´é", |
| | | label: "è¯ç¨æï¼æï¼", |
| | | prop: "probationPeriod", |
| | | width: 120, |
| | | }, |
| | | // { |
| | | // label: "è½¬æ£æ¥æ", |
| | | // prop: "probationEndDate", |
| | | // width: 120, |
| | | // formatData: (row) => { |
| | | // // ä¿®æ¹ä¸ºä½¿ç¨ååå¼å§æ¥æè®¡ç®è½¬æ£æ¥æ |
| | | // if (row.contractStartTime && row.probationPeriod) { |
| | | // // 计ç®è½¬æ£æ¥æï¼ååå¼å§æ¥æå ä¸è¯ç¨æææ°ï¼ |
| | | // return dayjs(row.contractStartTime).add(row.probationPeriod, 'month').format('YYYY-MM-DD'); |
| | | // } |
| | | // return ''; |
| | | // }, |
| | | // formatType: (row) => { |
| | | // // ä¿®æ¹ä¸ºä½¿ç¨ååå¼å§æ¥ææ£æ¥æ¯å¦ä¸´è¿è½¬æ£ï¼7天å
ï¼ |
| | | // if (row.contractStartTime && row.probationPeriod) { |
| | | // const probationEndDate = dayjs(row.contractStartTime).add(row.probationPeriod, 'month'); |
| | | // const daysUntilProbationEnd = probationEndDate.diff(dayjs(), 'day'); |
| | | |
| | | // if (daysUntilProbationEnd >= 0 && daysUntilProbationEnd <= 7) { |
| | | // return 'warning'; // 使ç¨è¦åæ ·å¼æ 记临è¿è½¬æ£çåå·¥ |
| | | // } |
| | | // } |
| | | // return ''; |
| | | // } |
| | | // }, |
| | | { |
| | | label: "ååå¹´éï¼å¹´ï¼", |
| | | prop: "contractTerm", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "ååå¼å§æ¥æ", |
| | |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | |
| | | // æ£æ¥æ¯å¦æä¸´è¿è½¬æ£çå工并æé |
| | | checkProbationEnding(tableData.value); |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | // æ£æ¥ä¸´è¿è½¬æ£çå工并æé |
| | | const checkProbationEnding = (data) => { |
| | | const probationEndingSoon = []; |
| | | |
| | | data.forEach(item => { |
| | | // ä¿®æ¹ä¸ºä½¿ç¨ååå¼å§æ¥ææ£æ¥ |
| | | if (item.contractStartTime && item.probationPeriod) { |
| | | const probationEndDate = dayjs(item.contractStartTime).add(item.probationPeriod, 'month'); |
| | | const daysUntilProbationEnd = probationEndDate.diff(dayjs(), 'day'); |
| | | |
| | | if (daysUntilProbationEnd >= 0 && daysUntilProbationEnd <= 7) { |
| | | probationEndingSoon.push({ |
| | | staffName: item.staffName, |
| | | probationEndDate: probationEndDate.format('YYYY-MM-DD'), |
| | | daysLeft: daysUntilProbationEnd |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | if (probationEndingSoon.length > 0) { |
| | | let message = '以ä¸åå·¥å°å¨7天å
转æ£ï¼\n'; |
| | | probationEndingSoon.forEach(item => { |
| | | message += `${item.staffName}ï¼${item.probationEndDate}ï¼è¿æ${item.daysLeft}天ï¼\n`; |
| | | }); |
| | | |
| | | // æ¾ç¤ºæéæ¶æ¯ |
| | | proxy.$modal.msgInfo(message); |
| | | } |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | |
| | | }, |
| | | }, |
| | | { |
| | | label: "å·²å¼ç¥¨éé¢(å
)", |
| | | label: "å·²æ¥ç¥¨éé¢(å
)", |
| | | prop: "receiptPaymentAmount", |
| | | width:200, |
| | | formatData: (val) => { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | label: "å¾
å¼ç¥¨éé¢(å
)", |
| | | label: "å¾
æ¥ç¥¨éé¢(å
)", |
| | | prop: "unReceiptPaymentAmount", |
| | | width:200, |
| | | formatData: (val) => { |
| | |
| | | :summary-method="summarizeMainTable" |
| | | @expand-change="expandChange" |
| | | height="calc(100vh - 18.5em)" |
| | | stripe |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column type="expand"> |
| | |
| | | border |
| | | show-summary |
| | | :summary-method="summarizeChildrenTable" |
| | | stripe |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | |
| | | :data="productData" |
| | | border |
| | | @selection-change="productSelected" |
| | | stripe |
| | | show-summary |
| | | style="width: 100%" |
| | | :summary-method="summarizeChildrenTable" |
| | |
| | | show-summary |
| | | v-loading="childrenLoading" |
| | | :summary-method="summarizeMainTable2" |
| | | stripe |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»è®°äººï¼" prop="registrant"> |
| | | <el-input |
| | | v-model="form.registrant" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | disabled |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¥æï¼" prop="paymentDate"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | | v-model="form.paymentDate" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¥æï¼" prop="paymentDate"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | | v-model="form.paymentDate" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»è®°äººï¼" prop="registrant"> |
| | | <el-input |
| | | v-model="form.registrant" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | disabled |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»è®°æ¥æï¼" prop="registrationtDate"> |
| | | <el-input |
| | |
| | | const isShowSummarySon = ref(true); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "éè´ååå·", |
| | | prop: "purchaseContractNumber", |
| | | }, |
| | | { |
| | | label: "仿¬¾æ¥æ", |
| | | prop: "paymentDate", |
| | | }, |
| | |
| | | height="calc(100vh - 18.5em)" |
| | | :highlight-current-row="true" |
| | | style="width: 100%" |
| | | stripe |
| | | tooltip-effect="dark" |
| | | @row-click="rowClick" |
| | | :show-summary="isShowSummary" |
| | |
| | | defineOptions({ |
| | | name: "æ¥ç¥¨å°è´¦è¡¨å", |
| | | }); |
| | | |
| | | const temFutureTickets = ref(0) |
| | | const { form, resetForm } = useFormData({ |
| | | id: undefined, |
| | | purchaseContractNumber: undefined, // éè´ååå· |
| | |
| | | form.ticketsAmount = data.ticketsAmount.toFixed(2); |
| | | form.taxInclusiveUnitPrice = data.taxInclusiveUnitPrice; |
| | | form.futureTickets = data.futureTickets; |
| | | temFutureTickets.value = data.futureTickets; |
| | | } |
| | | }; |
| | | |
| | |
| | | proxy.$modal.msgWarning("å«ç¨åä»·ä¸è½ä¸ºé¶ææªå®ä¹"); |
| | | return; |
| | | } |
| | | |
| | | if (Number(form.ticketsNum) > Number(form.futureTickets)) { |
| | | if (Number(form.ticketsNum) > Number(temFutureTickets.value)) { |
| | | proxy.$modal.msgWarning("å¼ç¥¨æ°ä¸å¾å¤§äºæªå¼ç¥¨æ°"); |
| | | form.ticketsNum = form.futureTickets |
| | | return; |
| | | form.ticketsNum = temFutureTickets.value |
| | | } |
| | | |
| | | // ç¡®ä¿æææ°å¼é½è½¬æ¢ä¸ºæ°åç±»åè¿è¡è®¡ç® |
| | | const ticketsAmount = Number(val) * Number(form.taxInclusiveUnitPrice); |
| | | const futureTickets = Number(form.futureTickets) - Number(val); |
| | | const ticketsAmount = Number(form.ticketsNum) * Number(form.taxInclusiveUnitPrice); |
| | | const futureTickets = Number(temFutureTickets.value) - Number(form.ticketsNum); |
| | | form.futureTickets = Number(futureTickets.toFixed(2)); |
| | | form.ticketsAmount = Number(ticketsAmount.toFixed(2)); |
| | | }; |
| | |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "客æ·åç§°", |
| | | prop: "customerName", |
| | | label: "项ç®åç§°", |
| | | prop: "projectName", |
| | | width: 240, |
| | | }, |
| | | { |
| | | label: "ä¾åºååç§°", |
| | | prop: "supplierName", |
| | | width: 240, |
| | | }, |
| | | { |
| | | label: "产å大类", |
| | | prop: "productCategory", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "è§æ ¼åå·", |
| | |
| | | }, |
| | | }, |
| | | { |
| | | label: "å¼ç¥¨æ¥æ", |
| | | label: "æ¬æ¬¡æ¥ç¥¨æ°", |
| | | prop: "ticketsNum", |
| | | width: 110, |
| | | }, |
| | | { |
| | | label: "æ¥ç¥¨æ¥æ", |
| | | prop: "createdAt", |
| | | width: 110, |
| | | }, |
| | | { |
| | | label: "å¼ç¥¨éé¢", |
| | | label: "æ¥ç¥¨éé¢(å
)", |
| | | prop: "ticketsAmount", |
| | | width: 200, |
| | | formatData: (cell) => { |
| | |
| | | :summary-method="summarizeMainTable" |
| | | @expand-change="expandChange" |
| | | height="calc(100vh - 18.5em)" |
| | | stripe |
| | | > |
| | | <el-table-column align="center" label="åºå·" type="index" width="55" /> |
| | | <el-table-column type="expand"> |
| | |
| | | border |
| | | show-summary |
| | | :summary-method="summarizeChildrenTable" |
| | | stripe |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | |
| | | <div class="table_list"> |
| | | <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;"> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢å°è´¦</el-button> |
| | | <el-button type="success" @click="openScanAddDialog">æ«ç æ°å¢</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | |
| | | :summary-method="summarizeMainTable" |
| | | @expand-change="expandChange" |
| | | height="calc(100vh - 18.5em)" |
| | | stripe |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column type="expand"> |
| | |
| | | border |
| | | show-summary |
| | | :summary-method="summarizeChildrenTable" |
| | | stripe |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | |
| | | <el-table-column |
| | | fixed="right" |
| | | label="æä½" |
| | | min-width="60" |
| | | min-width="150" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | |
| | | :disabled="scope.row.receiptPaymentAmount>0 || scope.row.recorderName !== userStore.nickName" |
| | | >ç¼è¾</el-button |
| | | > |
| | | <el-button |
| | | link |
| | | type="success" |
| | | size="small" |
| | | @click="showQRCode(scope.row)" |
| | | >çæäºç»´ç </el-button |
| | | > |
| | | |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | border |
| | | @selection-change="productSelected" |
| | | show-summary |
| | | stripe |
| | | :summary-method="summarizeProTable" |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- äºç»´ç æ¾ç¤ºå¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | v-model="qrCodeDialogVisible" |
| | | title="éè´ååå·äºç»´ç " |
| | | width="400px" |
| | | center |
| | | > |
| | | <div style="text-align: center;"> |
| | | <img :src="qrCodeUrl" alt="äºç»´ç " style="width:200px;height:200px;" /> |
| | | <div style="margin: 20px;"> |
| | | <el-button type="primary" @click="downloadQRCode">ä¸è½½äºç»´ç å¾ç</el-button> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | <!-- æ«ç æ°å¢å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | v-model="scanAddDialogVisible" |
| | | title="æ«ç æ°å¢éè´å°è´¦" |
| | | width="70%" |
| | | @close="closeScanAddDialog" |
| | | > |
| | | <el-form |
| | | :model="scanAddForm" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="scanAddRules" |
| | | ref="scanAddFormRef" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æ«ç å
容ï¼"> |
| | | <el-input |
| | | v-model="scanAddForm.scanContent" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="è¯·æ«æäºç»´ç ææå¨è¾å
¥éè´ååä¿¡æ¯" |
| | | @input="parseScanContent" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éè´ååå·ï¼" prop="purchaseContractNumber"> |
| | | <el-input |
| | | v-model="scanAddForm.purchaseContractNumber" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºååç§°ï¼" prop="supplierName"> |
| | | <el-input |
| | | v-model="scanAddForm.supplierName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项ç®åç§°ï¼" prop="projectName"> |
| | | <el-input |
| | | v-model="scanAddForm.projectName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ååéé¢(å
)ï¼" prop="contractAmount"> |
| | | <el-input-number |
| | | v-model="scanAddForm.contractAmount" |
| | | :precision="2" |
| | | :step="0.1" |
| | | clearable |
| | | style="width: 100%" |
| | | placeholder="请è¾å
¥" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼ï¼"> |
| | | <el-input |
| | | v-model="scanAddForm.paymentMethod" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å½å
¥äººï¼"> |
| | | <el-input v-model="scanAddForm.recorderName" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨ï¼"> |
| | | <el-input |
| | | v-model="scanAddForm.remark" |
| | | type="textarea" |
| | | :rows="2" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitScanAdd">确认æ°å¢</el-button> |
| | | <el-button @click="closeScanAddDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ«ç ç»è®°å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | v-model="scanDialogVisible" |
| | | title="æ«ç ç»è®°" |
| | | width="60%" |
| | | @close="closeScanDialog" |
| | | > |
| | | <el-form |
| | | :model="scanForm" |
| | | label-width="120px" |
| | | label-position="left" |
| | | :rules="scanRules" |
| | | ref="scanFormRef" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éè´ååå·ï¼"> |
| | | <el-input v-model="scanForm.purchaseContractNumber" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºååç§°ï¼"> |
| | | <el-input v-model="scanForm.supplierName" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项ç®åç§°ï¼"> |
| | | <el-input v-model="scanForm.projectName" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ«ç æ¶é´ï¼"> |
| | | <el-input v-model="scanForm.scanTime" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ«ç 人ï¼"> |
| | | <el-input v-model="scanForm.scannerName" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ«ç ç¶æï¼"> |
| | | <el-tag :type="scanForm.scanStatus === 'å·²æ«ç ' ? 'success' : 'warning'"> |
| | | {{ scanForm.scanStatus }} |
| | | </el-tag> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æ«ç 夿³¨ï¼"> |
| | | <el-input |
| | | v-model="scanForm.scanRemark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥æ«ç 夿³¨ä¿¡æ¯" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æ«ç è®°å½ï¼"> |
| | | <el-table :data="scanRecords" border style="width: 100%" stripe> |
| | | <el-table-column label="åºå·" type="index" width="60" align="center" /> |
| | | <el-table-column label="æ«ç æ¶é´" prop="scanTime" width="180" /> |
| | | <el-table-column label="æ«ç 人" prop="scannerName" width="120" /> |
| | | <el-table-column label="æ«ç ç¶æ" prop="scanStatus" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.scanStatus === 'å·²æ«ç ' ? 'success' : 'warning'"> |
| | | {{ scope.row.scanStatus }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="夿³¨" prop="scanRemark" /> |
| | | </el-table> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitScan">确认æ«ç </el-button> |
| | | <el-button @click="closeScanDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { getToken } from "@/utils/auth"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { ref, onMounted } from "vue"; |
| | | import { ref, onMounted, reactive, toRefs, getCurrentInstance, nextTick } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | |
| | | createPurchaseNo, |
| | | } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import useFormData from "@/hooks/useFormData.js"; |
| | | import QRCode from "qrcode"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const productData = ref([]); |
| | |
| | | import dayjs from "dayjs"; |
| | | |
| | | const userStore = useUserStore(); |
| | | |
| | | // äºç»´ç ç¸å
³åé |
| | | const qrCodeDialogVisible = ref(false); |
| | | const qrCodeUrl = ref(""); |
| | | |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | |
| | | // ä¸ä¼ åæ ¡æ£ |
| | | function handleBeforeUpload(file) { |
| | | // æ ¡æ£æä»¶å¤§å° |
| | | if (file.size > 1024 * 1024 * 10) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶
è¿10MB!"); |
| | | if (file.size > 1024 * 1024 * 50) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶
è¿50MB!"); |
| | | return false; |
| | | } |
| | | proxy.$modal.loading("æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å..."); |
| | |
| | | } |
| | | }; |
| | | |
| | | // æ¾ç¤ºäºç»´ç |
| | | const showQRCode = async (row) => { |
| | | try { |
| | | // æå»ºäºç»´ç å
容ï¼åªå
å«éè´ååå·ï¼çº¯ææ¬ï¼ |
| | | const qrContent = row.purchaseContractNumber || ''; |
| | | // æ£æ¥å
容æ¯å¦ä¸ºç©º |
| | | if (!qrContent || qrContent.trim() === '') { |
| | | proxy.$modal.msgWarning("è¯¥è¡æ²¡æéè´ååå·ï¼æ æ³çæäºç»´ç "); |
| | | return; |
| | | } |
| | | qrCodeUrl.value = await QRCode.toDataURL(qrContent, { |
| | | width: 200, |
| | | margin: 2, |
| | | color: { |
| | | dark: '#000000', |
| | | light: '#FFFFFF' |
| | | } |
| | | }); |
| | | qrCodeDialogVisible.value = true; |
| | | } catch (error) { |
| | | console.error('çæäºç»´ç 失败:', error); |
| | | proxy.$modal.msgError("çæäºç»´ç 失败ï¼" + error.message); |
| | | } |
| | | }; |
| | | |
| | | // ä¸è½½äºç»´ç |
| | | const downloadQRCode = () => { |
| | | if (!qrCodeUrl.value) { |
| | | proxy.$modal.msgWarning("äºç»´ç æªçæ"); |
| | | return; |
| | | } |
| | | |
| | | const a = document.createElement('a'); |
| | | a.href = qrCodeUrl.value; |
| | | a.download = `éè´ååå·äºç»´ç _${new Date().getTime()}.png`; |
| | | document.body.appendChild(a); |
| | | a.click(); |
| | | document.body.removeChild(a); |
| | | proxy.$modal.msgSuccess("ä¸è½½æå"); |
| | | }; |
| | | |
| | | // æ«ç æ°å¢å¯¹è¯æ¡ç¸å
³åé |
| | | const scanAddDialogVisible = ref(false); |
| | | const scanAddForm = reactive({ |
| | | scanContent: "", |
| | | purchaseContractNumber: "", |
| | | supplierName: "", |
| | | projectName: "", |
| | | contractAmount: "", |
| | | paymentMethod: "", |
| | | recorderName: "", |
| | | scanRemark: "", |
| | | }); |
| | | const scanAddRules = { |
| | | purchaseContractNumber: [{ required: true, message: "请è¾å
¥éè´ååå·", trigger: "blur" }], |
| | | supplierName: [{ required: true, message: "请è¾å
¥ä¾åºååç§°", trigger: "blur" }], |
| | | projectName: [{ required: true, message: "请è¾å
¥é¡¹ç®åç§°", trigger: "blur" }], |
| | | }; |
| | | |
| | | // æ«ç ç»è®°å¯¹è¯æ¡ç¸å
³åé |
| | | const scanDialogVisible = ref(false); |
| | | const scanForm = reactive({ |
| | | purchaseContractNumber: "", |
| | | supplierName: "", |
| | | projectName: "", |
| | | scanTime: "", |
| | | scannerName: "", |
| | | scanStatus: "æªæ«ç ", |
| | | scanRemark: "", |
| | | }); |
| | | const scanRules = { |
| | | scanRemark: [{ required: true, message: "请è¾å
¥æ«ç 夿³¨", trigger: "blur" }], |
| | | }; |
| | | const scanRecords = ref([]); |
| | | |
| | | // æå¼æ«ç æ°å¢å¯¹è¯æ¡ |
| | | const openScanAddDialog = () => { |
| | | scanAddForm.scanContent = ""; |
| | | scanAddForm.purchaseContractNumber = ""; |
| | | scanAddForm.supplierName = ""; |
| | | scanAddForm.projectName = ""; |
| | | scanAddForm.contractAmount = ""; |
| | | scanAddForm.paymentMethod = ""; |
| | | scanAddForm.recorderName = userStore.nickName; |
| | | scanAddForm.scanRemark = ""; |
| | | scanAddDialogVisible.value = true; |
| | | }; |
| | | |
| | | // è§£ææ«ç å
å®¹ï¼æ¨¡æè§£æäºç»´ç æ°æ®ï¼ |
| | | const parseScanContent = (content) => { |
| | | if (!content) return; |
| | | |
| | | // 模æè§£æäºç»´ç å
容ï¼è¿éå¯ä»¥æ ¹æ®å®é
éæ±è°æ´è§£æé»è¾ |
| | | // å设æ«ç å
å®¹æ ¼å¼ä¸ºï¼ååå·|ä¾åºå|项ç®|éé¢|仿¬¾æ¹å¼ |
| | | const parts = content.split('|'); |
| | | if (parts.length >= 3) { |
| | | scanAddForm.purchaseContractNumber = parts[0] || ""; |
| | | scanAddForm.supplierName = parts[1] || ""; |
| | | scanAddForm.projectName = parts[2] || ""; |
| | | scanAddForm.contractAmount = parts[3] || ""; |
| | | scanAddForm.paymentMethod = parts[4] || ""; |
| | | } |
| | | }; |
| | | |
| | | // å
³éæ«ç æ°å¢å¯¹è¯æ¡ |
| | | const closeScanAddDialog = () => { |
| | | scanAddDialogVisible.value = false; |
| | | proxy.resetForm("scanAddFormRef"); |
| | | }; |
| | | |
| | | // æäº¤æ«ç æ°å¢ |
| | | const submitScanAdd = () => { |
| | | proxy.$refs["scanAddFormRef"].validate((valid) => { |
| | | if (valid) { |
| | | // æå»ºæ°å¢æ°æ® |
| | | const newData = { |
| | | purchaseContractNumber: scanAddForm.purchaseContractNumber, |
| | | supplierName: scanAddForm.supplierName, |
| | | projectName: scanAddForm.projectName, |
| | | contractAmount: scanAddForm.contractAmount, |
| | | paymentMethod: scanAddForm.paymentMethod, |
| | | recorderName: scanAddForm.recorderName, |
| | | entryDate: getCurrentDate(), |
| | | remark: scanAddForm.scanRemark, |
| | | type: 2 |
| | | }; |
| | | |
| | | // æ¨¡ææ°å¢æå |
| | | proxy.$modal.msgSuccess("æ«ç æ°å¢æåï¼"); |
| | | closeScanAddDialog(); |
| | | |
| | | // å¯ä»¥éæ©æ¯å¦å·æ°å表 |
| | | // getList(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // æå¼æ«ç ç»è®°å¯¹è¯æ¡ |
| | | const openScanDialog = (row) => { |
| | | scanForm.purchaseContractNumber = row.purchaseContractNumber; |
| | | scanForm.supplierName = row.supplierName; |
| | | scanForm.projectName = row.projectName; |
| | | scanForm.scanTime = getCurrentDateTime(); |
| | | scanForm.scannerName = userStore.nickName; |
| | | scanForm.scanStatus = "æªæ«ç "; |
| | | scanForm.scanRemark = ""; |
| | | scanRecords.value = []; |
| | | scanDialogVisible.value = true; |
| | | }; |
| | | |
| | | // å
³éæ«ç ç»è®°å¯¹è¯æ¡ |
| | | const closeScanDialog = () => { |
| | | scanDialogVisible.value = false; |
| | | proxy.resetForm("scanFormRef"); |
| | | }; |
| | | |
| | | // æäº¤æ«ç ç»è®° |
| | | const submitScan = () => { |
| | | proxy.$refs["scanFormRef"].validate((valid) => { |
| | | if (valid) { |
| | | // æ·»å æ«ç è®°å½ |
| | | scanRecords.value.push({ |
| | | ...scanForm, |
| | | id: Date.now(), // 模æID |
| | | scanTime: getCurrentDateTime(), |
| | | }); |
| | | scanForm.scanStatus = "å·²æ«ç "; |
| | | scanForm.scanRemark = scanForm.scanRemark || "æ "; |
| | | proxy.$modal.msgSuccess("æ«ç ç»è®°æåï¼"); |
| | | closeScanDialog(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // è·åå½åæ¥ææ¶é´ |
| | | function getCurrentDateTime() { |
| | | const now = new Date(); |
| | | const year = now.getFullYear(); |
| | | const month = String(now.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(now.getDate()).padStart(2, "0"); |
| | | const hours = String(now.getHours()).padStart(2, "0"); |
| | | const minutes = String(now.getMinutes()).padStart(2, "0"); |
| | | const seconds = String(now.getSeconds()).padStart(2, "0"); |
| | | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; |
| | | } |
| | | |
| | | |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | |
| | | > |
| | | <el-button type="primary" @click="addRow" style="margin-bottom: 10px;">æ°å¢</el-button> |
| | | <span style="font-size: 18px;margin-left: 10px">å¾
æäº§æ°éï¼{{pendingNum}}</span> |
| | | <el-table :data="tableData" border style="width: 100%" :summary-method="summarizeMainTable" show-summary :row-key="row => row.id"> |
| | | <el-table :data="tableData" border style="width: 100%" :summary-method="summarizeMainTable" show-summary :row-key="row => row.id" stripe> |
| | | <el-table-column label="åºå·" width="60"> |
| | | <template #default="scope"> |
| | | {{ scope.$index + 1 }} |
| | |
| | | :data="expandData" |
| | | border |
| | | show-summary |
| | | stripe |
| | | :summary-method="summarizeMainTable" |
| | | v-loading="childrenLoading" |
| | | > |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="safety-monitoring"> |
| | | <el-row :gutter="20"> |
| | | <!-- 左侧ï¼å®æ¶çæ§åºå --> |
| | | <el-col :span="16"> |
| | | <el-card class="monitoring-card"> |
| | | <div slot="header" class="card-header"> |
| | | <span>宿¶æ°ä½æµåº¦çæ§</span> |
| | | <el-tag :type="systemStatus === 'normal' ? 'success' : 'danger'"> |
| | | {{ systemStatus === 'normal' ? 'ç³»ç»æ£å¸¸' : 'ç³»ç»åè¦' }} |
| | | </el-tag> |
| | | </div> |
| | | |
| | | <!-- å¨ç½åºçæ§ --> |
| | | <div class="monitoring-section"> |
| | | <h3>å¨ç½åºçæ§</h3> |
| | | <div class="sensor-grid"> |
| | | <div class="sensor-item" v-for="sensor in tankSensors" :key="sensor.id"> |
| | | <div class="sensor-header"> |
| | | <span>{{ sensor.name }}</span> |
| | | <el-tag :type="sensor.status === 'normal' ? 'success' : 'danger'" size="small"> |
| | | {{ sensor.status === 'normal' ? 'æ£å¸¸' : 'è¶
æ ' }} |
| | | </el-tag> |
| | | </div> |
| | | <div class="sensor-data"> |
| | | <div class="data-item"> |
| | | <span>ç²ç·: {{ sensor.methane.toFixed(2) }}%</span> |
| | | <el-progress |
| | | :percentage="Math.min(Math.round(sensor.methane * 40 * 100) / 100, 100)" |
| | | :color="getProgressColor(Math.min(Math.round(sensor.methane * 40 * 100) / 100, 100), 80)" |
| | | :format="formatProgress" |
| | | :stroke-width="8" |
| | | /> |
| | | </div> |
| | | <div class="data-item"> |
| | | <span>ç¡«åæ°¢: {{ sensor.h2s.toFixed(2) }}ppm</span> |
| | | <el-progress |
| | | :percentage="Math.min(Math.round((sensor.h2s / 20) * 100 * 100) / 100, 100)" |
| | | :color="getProgressColor(Math.min(Math.round((sensor.h2s / 20) * 100 * 100) / 100, 100), 80)" |
| | | :format="formatProgress" |
| | | :stroke-width="8" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- äºå£å缩æºçæ§ --> |
| | | <div class="monitoring-section"> |
| | | <h3>äºå£å缩æºçæ§</h3> |
| | | <div class="sensor-grid"> |
| | | <div class="sensor-item" v-for="sensor in compressorSensors" :key="sensor.id"> |
| | | <div class="sensor-header"> |
| | | <span>{{ sensor.name }}</span> |
| | | <el-tag :type="sensor.status === 'normal' ? 'success' : 'danger'" size="small"> |
| | | {{ sensor.status === 'normal' ? 'æ£å¸¸' : 'è¶
æ ' }} |
| | | </el-tag> |
| | | </div> |
| | | <div class="sensor-data"> |
| | | <div class="data-item"> |
| | | <span>ç²ç·: {{ sensor.methane.toFixed(2) }}%</span> |
| | | <el-progress |
| | | :percentage="Math.min(Math.round(sensor.methane * 40 * 100) / 100, 100)" |
| | | :color="getProgressColor(sensor.methane, 2.5)" |
| | | :format="formatProgress" |
| | | :stroke-width="8" |
| | | /> |
| | | </div> |
| | | <div class="data-item"> |
| | | <span>ç¡«åæ°¢: {{ sensor.h2s.toFixed(2) }}ppm</span> |
| | | <el-progress |
| | | :percentage="Math.min(Math.round((sensor.h2s / 20) * 100 * 100) / 100, 100)" |
| | | :color="getProgressColor(sensor.h2s, 10)" |
| | | :format="formatProgress" |
| | | :stroke-width="8" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 宿¶æ²çº¿å¾ --> |
| | | <div class="chart-section"> |
| | | <h3>宿¶æµåº¦æ²çº¿</h3> |
| | | <div class="chart-container"> |
| | | <div ref="chart" class="chart"></div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- å³ä¾§ï¼æ§å¶é¢æ¿ --> |
| | | <el-col :span="8"> |
| | | <el-card class="control-card"> |
| | | <div slot="header" class="card-header"> |
| | | <span>åºæ¥æ§å¶é¢æ¿</span> |
| | | </div> |
| | | |
| | | <!-- å·æ·ç¶æ --> |
| | | <div class="control-section"> |
| | | <h4>å·æ·ç³»ç»ç¶æ</h4> |
| | | <div class="status-grid"> |
| | | <div class="status-item" v-for="sprinkler in sprinklerSystems" :key="sprinkler.id"> |
| | | <div class="status-indicator" :class="sprinkler.status"> |
| | | <i class="el-icon-circle-check" v-if="sprinkler.status === 'active'"></i> |
| | | <i class="el-icon-circle-close" v-else></i> |
| | | </div> |
| | | <span>{{ sprinkler.name }}</span> |
| | | <el-tag :type="sprinkler.status === 'active' ? 'success' : 'info'" size="small"> |
| | | {{ sprinkler.status === 'active' ? 'è¿è¡ä¸' : 'å¾
æº' }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- åºæ¥è®°å½æé® --> |
| | | <h4>åºæ¥ç®¡ç</h4> |
| | | |
| | | <div class="control-section1"> |
| | | <el-button type="primary" @click="showEmergencyRecords" style="margin-bottom: 10px;"> |
| | | åºæ¥è®°å½ |
| | | </el-button> |
| | | <el-button type="warning" @click="triggerEmergency" :disabled="!hasEmergency"> |
| | | 触ååºæ¥ååº |
| | | </el-button> |
| | | </div> |
| | | |
| | | <!-- ç³»ç»æ¥å¿ --> |
| | | <div class="control-section"> |
| | | <h4>ç³»ç»æ¥å¿</h4> |
| | | <div class="log-container"> |
| | | <div class="log-item" v-for="log in systemLogs" :key="log.id"> |
| | | <span class="log-time">{{ log.time }}</span> |
| | | <span class="log-content">{{ log.content }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- æ³æ¼é¢è¦å¼¹çª --> |
| | | <el-dialog |
| | | title="â ï¸ æ³æ¼é¢è¦" |
| | | :visible.sync="leakWarningVisible" |
| | | width="500px" |
| | | :close-on-click-modal="false" |
| | | :close-on-press-escape="false" |
| | | class="leak-warning-dialog" |
| | | > |
| | | <div class="warning-content"> |
| | | <div class="warning-icon"> |
| | | <i class="el-icon-warning"></i> |
| | | </div> |
| | | <div class="warning-text"> |
| | | <h3>æ£æµå°æ°ä½æµåº¦è¶
æ ï¼</h3> |
| | | <p>ä½ç½®ï¼{{ currentWarning.location }}</p> |
| | | <p>è¶
æ æ°ä½ï¼{{ currentWarning.gas }}</p> |
| | | <p>å½åæµåº¦ï¼{{ currentWarning.value }}</p> |
| | | </div> |
| | | </div> |
| | | <div slot="footer" class="dialog-footer"> |
| | | <el-button type="danger" @click="acknowledgeWarning">确认åè¦</el-button> |
| | | <el-button type="primary" @click="viewDetails">æ¥ç详æ
</el-button> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | <!-- åºæ¥è®°å½å¼¹çª --> |
| | | <el-dialog |
| | | title="åºæ¥è®°å½" |
| | | :visible.sync="emergencyRecordsVisible" |
| | | width="800px" |
| | | > |
| | | <el-table :data="emergencyRecords" style="width: 100%" stripe> |
| | | <el-table-column prop="time" label="æ¶é´" width="180"></el-table-column> |
| | | <el-table-column prop="location" label="ä½ç½®" width="150"></el-table-column> |
| | | <el-table-column prop="type" label="ç±»å" width="120"></el-table-column> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <template slot-scope="scope"> |
| | | <el-tag :type="scope.row.status === 'resolved' ? 'success' : 'warning'"> |
| | | {{ scope.row.status === 'resolved' ? '已解å³' : 'å¤çä¸' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="description" label="æè¿°"></el-table-column> |
| | | <el-table-column label="æä½" width="120"> |
| | | <template slot-scope="scope"> |
| | | <el-button type="text" @click="viewBlockchainDetails(scope.row)"> |
| | | åºåé¾è¯¦æ
|
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-dialog> |
| | | |
| | | <!-- åºåé¾åè¯è¯¦æ
å¼¹çª --> |
| | | <el-dialog |
| | | title="åºåé¾åè¯è¯¦æ
" |
| | | :visible.sync="blockchainDetailsVisible" |
| | | width="900px" |
| | | > |
| | | <div class="blockchain-details"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="äºä»¶ID">{{ currentEvent.id }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¶é´æ³">{{ currentEvent.timestamp }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä½ç½®">{{ currentEvent.location }}</el-descriptions-item> |
| | | <el-descriptions-item label="äºä»¶ç±»å">{{ currentEvent.type }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="sensor-data-section"> |
| | | <h4>ä¼ æå¨æ°æ®</h4> |
| | | <el-table :data="currentEvent.sensorData" style="width: 100%" stripe> |
| | | <el-table-column prop="sensor" label="ä¼ æå¨"></el-table-column> |
| | | <el-table-column prop="methane" label="ç²ç·æµåº¦"></el-table-column> |
| | | <el-table-column prop="h2s" label="ç¡«åæ°¢æµåº¦"></el-table-column> |
| | | <el-table-column prop="timestamp" label="è®°å½æ¶é´"></el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div class="action-log-section"> |
| | | <h4>å¤ç½®å¨ä½è®°å½</h4> |
| | | <el-timeline> |
| | | <el-timeline-item |
| | | v-for="action in currentEvent.actions" |
| | | :key="action.id" |
| | | :timestamp="action.timestamp" |
| | | :type="action.type === 'emergency' ? 'danger' : 'primary'" |
| | | > |
| | | {{ action.description }} |
| | | </el-timeline-item> |
| | | </el-timeline> |
| | | </div> |
| | | |
| | | <div class="blockchain-info"> |
| | | <h4>åºåé¾ä¿¡æ¯</h4> |
| | | <el-descriptions :column="1" border> |
| | | <el-descriptions-item label="åºååå¸">{{ currentEvent.blockHash }}</el-descriptions-item> |
| | | <el-descriptions-item label="交æåå¸">{{ currentEvent.txHash }}</el-descriptions-item> |
| | | <el-descriptions-item label="确认æ°">{{ currentEvent.confirmations }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import * as echarts from 'echarts' |
| | | |
| | | export default { |
| | | name: 'SafetyMonitoring', |
| | | data() { |
| | | return { |
| | | systemStatus: 'normal', |
| | | leakWarningVisible: false, |
| | | emergencyRecordsVisible: false, |
| | | blockchainDetailsVisible: false, |
| | | currentWarning: {}, |
| | | currentEvent: {}, |
| | | hasEmergency: false, |
| | | |
| | | // å¨ç½åºä¼ æå¨æ°æ® |
| | | tankSensors: [ |
| | | { id: 1, name: 'å¨ç½T-001', methane: 1.20, h2s: 2.10, status: 'normal' }, |
| | | { id: 2, name: 'å¨ç½T-002', methane: 0.80, h2s: 1.50, status: 'normal' }, |
| | | { id: 3, name: 'å¨ç½T-003', methane: 3.20, h2s: 8.50, status: 'warning' }, |
| | | { id: 4, name: 'å¨ç½T-004', methane: 0.60, h2s: 0.80, status: 'normal' } |
| | | ], |
| | | |
| | | // äºå£å缩æºä¼ æå¨æ°æ® |
| | | compressorSensors: [ |
| | | { id: 5, name: 'å缩æºC-001', methane: 2.10, h2s: 3.20, status: 'normal' }, |
| | | { id: 6, name: 'å缩æºC-002', methane: 4.80, h2s: 12.50, status: 'warning' }, |
| | | { id: 7, name: 'å缩æºC-003', methane: 1.80, h2s: 2.80, status: 'normal' } |
| | | ], |
| | | |
| | | // å·æ·ç³»ç»ç¶æ |
| | | sprinklerSystems: [ |
| | | { id: 1, name: 'å¨ç½åºå·æ·', status: 'active' }, |
| | | { id: 2, name: 'å缩æºåºå·æ·', status: 'standby' }, |
| | | { id: 3, name: 'ç´§æ¥å·æ·', status: 'standby' } |
| | | ], |
| | | |
| | | // ç³»ç»æ¥å¿ |
| | | systemLogs: [ |
| | | { id: 1, time: '14:30:25', content: 'ç³»ç»å¯å¨å®æï¼ææä¼ æå¨æ£å¸¸' }, |
| | | { id: 2, time: '14:35:12', content: 'å¨ç½T-003ç²ç·æµåº¦è¶
æ ï¼è§¦åé¢è¦' }, |
| | | { id: 3, time: '14:35:15', content: 'å¯å¨å¨ç½åºå·æ·ç³»ç»' }, |
| | | { id: 4, time: '14:35:20', content: 'åéç´§æ¥çæ£å¹¿æ' } |
| | | ], |
| | | |
| | | // åºæ¥è®°å½ |
| | | emergencyRecords: [ |
| | | { |
| | | id: 'EM001', |
| | | time: '2024-01-15 14:35:12', |
| | | location: 'å¨ç½T-003', |
| | | type: 'ç²ç·è¶
æ ', |
| | | status: 'resolved', |
| | | description: 'å¨ç½T-003ç²ç·æµåº¦è¾¾å°3.2%ï¼è¶
è¿å®å
¨éå¼2.5%' |
| | | }, |
| | | { |
| | | id: 'EM002', |
| | | time: '2024-01-15 14:35:15', |
| | | location: 'å缩æºC-002', |
| | | type: 'ç¡«åæ°¢è¶
æ ', |
| | | status: 'processing', |
| | | description: 'å缩æºC-002ç¡«åæ°¢æµåº¦è¾¾å°12.5ppmï¼è¶
è¿å®å
¨éå¼10ppm' |
| | | } |
| | | ], |
| | | |
| | | // å¾è¡¨å®ä¾ |
| | | chart: null, |
| | | |
| | | // 宿¶å¨ |
| | | timer: null |
| | | } |
| | | }, |
| | | |
| | | mounted() { |
| | | this.initChart() |
| | | this.startDataRefresh() |
| | | this.checkEmergencyStatus() |
| | | }, |
| | | |
| | | beforeDestroy() { |
| | | if (this.timer) { |
| | | clearInterval(this.timer) |
| | | } |
| | | if (this.chart) { |
| | | this.chart.dispose() |
| | | } |
| | | }, |
| | | |
| | | methods: { |
| | | // ç»ä¸è¿åº¦æ¡æ ¼å¼å为两ä½å°æ°ï¼é¿å
æµ®ç¹è¯¯å·®æ¾ç¤º |
| | | formatProgress(percentage) { |
| | | if (percentage == null || isNaN(percentage)) return '0.00%' |
| | | const val = Math.round(Number(percentage) * 100) / 100 |
| | | return `${val.toFixed(2)}%` |
| | | }, |
| | | // åå§åå¾è¡¨ |
| | | initChart() { |
| | | this.chart = echarts.init(this.$refs.chart) |
| | | this.updateChart() |
| | | }, |
| | | |
| | | // æ´æ°å¾è¡¨æ°æ® |
| | | updateChart() { |
| | | const option = { |
| | | title: { |
| | | text: '宿¶æ°ä½æµåº¦çæ§', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'cross' |
| | | } |
| | | }, |
| | | legend: { |
| | | data: ['å¨ç½åºç²ç·', 'å¨ç½åºç¡«åæ°¢', 'å缩æºç²ç·', 'å缩æºç¡«åæ°¢'], |
| | | top: 30 |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | top: '15%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: this.generateTimeData() |
| | | }, |
| | | yAxis: [ |
| | | { |
| | | type: 'value', |
| | | name: 'ç²ç·æµåº¦(%)', |
| | | position: 'left' |
| | | }, |
| | | { |
| | | type: 'value', |
| | | name: 'ç¡«åæ°¢æµåº¦(ppm)', |
| | | position: 'right' |
| | | } |
| | | ], |
| | | series: [ |
| | | { |
| | | name: 'å¨ç½åºç²ç·', |
| | | type: 'line', |
| | | data: this.generateRandomData(20, 0.5, 3.5), |
| | | smooth: true, |
| | | yAxisIndex: 0 |
| | | }, |
| | | { |
| | | name: 'å¨ç½åºç¡«åæ°¢', |
| | | type: 'line', |
| | | data: this.generateRandomData(20, 0.5, 12), |
| | | smooth: true, |
| | | yAxisIndex: 1 |
| | | }, |
| | | { |
| | | name: 'å缩æºç²ç·', |
| | | type: 'line', |
| | | data: this.generateRandomData(20, 1.0, 5.0), |
| | | smooth: true, |
| | | yAxisIndex: 0 |
| | | }, |
| | | { |
| | | name: 'å缩æºç¡«åæ°¢', |
| | | type: 'line', |
| | | data: this.generateRandomData(20, 1.0, 15), |
| | | smooth: true, |
| | | yAxisIndex: 1 |
| | | } |
| | | ] |
| | | } |
| | | |
| | | this.chart.setOption(option) |
| | | }, |
| | | |
| | | // çææ¶é´æ°æ® |
| | | generateTimeData() { |
| | | const times = [] |
| | | const now = new Date() |
| | | for (let i = 19; i >= 0; i--) { |
| | | const time = new Date(now.getTime() - i * 5 * 60 * 1000) |
| | | times.push(time.toLocaleTimeString('zh-CN', { hour12: false })) |
| | | } |
| | | return times |
| | | }, |
| | | |
| | | // çæéæºæ°æ® |
| | | generateRandomData(count, min, max) { |
| | | const data = [] |
| | | for (let i = 0; i < count; i++) { |
| | | data.push(+(Math.random() * (max - min) + min).toFixed(2)) |
| | | } |
| | | return data |
| | | }, |
| | | |
| | | // å¼å§æ°æ®å·æ° |
| | | startDataRefresh() { |
| | | this.timer = setInterval(() => { |
| | | this.refreshSensorData() |
| | | this.updateChart() |
| | | this.checkEmergencyStatus() |
| | | }, 5000) // æ¯5ç§å·æ°ä¸æ¬¡ |
| | | }, |
| | | |
| | | // å·æ°ä¼ æå¨æ°æ® |
| | | refreshSensorData() { |
| | | // æ´æ°å¨ç½åºä¼ æå¨æ°æ® |
| | | this.tankSensors.forEach(sensor => { |
| | | sensor.methane = +(Math.random() * 4).toFixed(2) |
| | | sensor.h2s = +(Math.random() * 15).toFixed(2) |
| | | sensor.status = this.getSensorStatus(sensor.methane, sensor.h2s) |
| | | }) |
| | | |
| | | // æ´æ°å缩æºä¼ æå¨æ°æ® |
| | | this.compressorSensors.forEach(sensor => { |
| | | sensor.methane = +(Math.random() * 6).toFixed(2) |
| | | sensor.h2s = +(Math.random() * 20).toFixed(2) |
| | | sensor.status = this.getSensorStatus(sensor.methane, sensor.h2s) |
| | | }) |
| | | |
| | | // æ£æ¥æ¯å¦éè¦è§¦åé¢è¦ |
| | | this.checkLeakWarning() |
| | | }, |
| | | |
| | | // è·åä¼ æå¨ç¶æ |
| | | getSensorStatus(methane, h2s) { |
| | | const methanePct = Math.min(Math.round(methane * 40 * 100) / 100, 100) |
| | | const h2sPct = Math.min(Math.round((h2s / 20) * 100 * 100) / 100, 100) |
| | | if (methanePct >= 80 || h2sPct >= 80) { |
| | | return 'warning' |
| | | } |
| | | return 'normal' |
| | | }, |
| | | |
| | | // æ£æ¥æ³æ¼é¢è¦ |
| | | checkLeakWarning() { |
| | | const allSensors = [...this.tankSensors, ...this.compressorSensors] |
| | | const warningSensor = allSensors.find(sensor => this.getSensorStatus(sensor.methane, sensor.h2s) === 'warning') |
| | | |
| | | if (warningSensor && !this.leakWarningVisible) { |
| | | this.triggerLeakWarning(warningSensor) |
| | | } |
| | | }, |
| | | |
| | | // è§¦åæ³æ¼é¢è¦ |
| | | triggerLeakWarning(sensor) { |
| | | const methanePct = Math.min(Math.round(sensor.methane * 40 * 100) / 100, 100) |
| | | const h2sPct = Math.min(Math.round((sensor.h2s / 20) * 100 * 100) / 100, 100) |
| | | const isMethaneMajor = methanePct >= h2sPct |
| | | const overGas = isMethaneMajor ? 'ç²ç·' : 'ç¡«åæ°¢' |
| | | const percent = (isMethaneMajor ? methanePct : h2sPct).toFixed(2) |
| | | this.currentWarning = { |
| | | location: sensor.name, |
| | | gas: overGas, |
| | | value: `${percent}%` |
| | | } |
| | | |
| | | this.leakWarningVisible = true |
| | | this.hasEmergency = true |
| | | |
| | | // èªå¨è§¦ååºæ¥ååº |
| | | this.autoEmergencyResponse(sensor) |
| | | |
| | | // æ·»å ç³»ç»æ¥å¿ |
| | | this.addSystemLog(`æ£æµå°${sensor.name}æ°ä½æµåº¦è¶
æ ï¼è§¦åæ³æ¼é¢è¦`) |
| | | }, |
| | | |
| | | // èªå¨åºæ¥ååº |
| | | autoEmergencyResponse(sensor) { |
| | | // å¯å¨å·æ·ç³»ç» |
| | | if (sensor.name.includes('å¨ç½')) { |
| | | this.sprinklerSystems[0].status = 'active' |
| | | } else if (sensor.name.includes('å缩æº')) { |
| | | this.sprinklerSystems[1].status = 'active' |
| | | } |
| | | |
| | | // æ·»å ç³»ç»æ¥å¿ |
| | | this.addSystemLog(`å¯å¨${sensor.name}åºåå·æ·ç³»ç»`) |
| | | this.addSystemLog(`åéç´§æ¥çæ£å¹¿æ`) |
| | | |
| | | // åå»ºåºæ¥è®°å½ |
| | | this.createEmergencyRecord(sensor) |
| | | }, |
| | | |
| | | // æ·»å ç³»ç»æ¥å¿ |
| | | addSystemLog(content) { |
| | | const now = new Date() |
| | | const time = now.toLocaleTimeString('zh-CN', { hour12: false }) |
| | | |
| | | this.systemLogs.unshift({ |
| | | id: Date.now(), |
| | | time: time, |
| | | content: content |
| | | }) |
| | | |
| | | // ä¿ææå¤20æ¡æ¥å¿ |
| | | if (this.systemLogs.length > 20) { |
| | | this.systemLogs = this.systemLogs.slice(0, 20) |
| | | } |
| | | }, |
| | | |
| | | // åå»ºåºæ¥è®°å½ |
| | | createEmergencyRecord(sensor) { |
| | | const now = new Date() |
| | | const record = { |
| | | id: `EM${Date.now()}`, |
| | | time: now.toLocaleString('zh-CN'), |
| | | location: sensor.name, |
| | | type: sensor.methane > 2.5 ? 'ç²ç·è¶
æ ' : 'ç¡«åæ°¢è¶
æ ', |
| | | status: 'processing', |
| | | description: `${sensor.name}æ£æµå°${sensor.methane > 2.5 ? 'ç²ç·' : 'ç¡«åæ°¢'}æµåº¦è¶
æ ` |
| | | } |
| | | |
| | | this.emergencyRecords.unshift(record) |
| | | }, |
| | | |
| | | // è·åè¿åº¦æ¡é¢è² |
| | | getProgressColor(value, threshold) { |
| | | if (value > threshold) { |
| | | return '#F56C6C' |
| | | } else if (value > threshold * 0.8) { |
| | | return '#E6A23C' |
| | | } |
| | | return '#67C23A' |
| | | }, |
| | | |
| | | // æ£æ¥åºæ¥ç¶æ |
| | | checkEmergencyStatus() { |
| | | const allSensors = [...this.tankSensors, ...this.compressorSensors] |
| | | const has = allSensors.some(sensor => this.getSensorStatus(sensor.methane, sensor.h2s) === 'warning') |
| | | this.hasEmergency = has |
| | | this.systemStatus = has ? 'warning' : 'normal' |
| | | }, |
| | | |
| | | // 确认åè¦ |
| | | acknowledgeWarning() { |
| | | this.leakWarningVisible = false |
| | | this.addSystemLog('æ³æ¼é¢è¦å·²ç¡®è®¤') |
| | | }, |
| | | |
| | | // æ¥ç详æ
|
| | | viewDetails() { |
| | | this.leakWarningVisible = false |
| | | // è¿éå¯ä»¥è·³è½¬å°è¯¦ç»é¡µé¢ææ¾ç¤ºæ´å¤ä¿¡æ¯ |
| | | }, |
| | | |
| | | // æ¾ç¤ºåºæ¥è®°å½ |
| | | showEmergencyRecords() { |
| | | this.emergencyRecordsVisible = true |
| | | }, |
| | | |
| | | // æ¥çåºåé¾è¯¦æ
|
| | | viewBlockchainDetails(record) { |
| | | this.currentEvent = { |
| | | id: record.id, |
| | | timestamp: record.time, |
| | | location: record.location, |
| | | type: record.type, |
| | | sensorData: [ |
| | | { |
| | | sensor: 'ç²ç·ä¼ æå¨', |
| | | methane: '3.2%', |
| | | h2s: '8.5ppm', |
| | | timestamp: record.time |
| | | }, |
| | | { |
| | | sensor: 'ç¡«åæ°¢ä¼ æå¨', |
| | | methane: '2.8%', |
| | | h2s: '12.5ppm', |
| | | timestamp: record.time |
| | | } |
| | | ], |
| | | actions: [ |
| | | { |
| | | id: 1, |
| | | timestamp: record.time, |
| | | type: 'emergency', |
| | | description: 'æ£æµå°æ°ä½æµåº¦è¶
æ ï¼è§¦åé¢è¦' |
| | | }, |
| | | { |
| | | id: 2, |
| | | timestamp: new Date(new Date(record.time).getTime() + 3000).toLocaleString('zh-CN'), |
| | | type: 'action', |
| | | description: 'å¯å¨å·æ·ç³»ç»é温' |
| | | }, |
| | | { |
| | | id: 3, |
| | | timestamp: new Date(new Date(record.time).getTime() + 5000).toLocaleString('zh-CN'), |
| | | type: 'action', |
| | | description: 'åéç´§æ¥çæ£å¹¿æ' |
| | | } |
| | | ], |
| | | blockHash: '0x1234567890abcdef...', |
| | | txHash: '0xabcdef1234567890...', |
| | | confirmations: 12 |
| | | } |
| | | |
| | | this.emergencyRecordsVisible = false |
| | | this.blockchainDetailsVisible = true |
| | | }, |
| | | |
| | | // 触ååºæ¥ååº |
| | | triggerEmergency() { |
| | | this.$message.success('åºæ¥ååºå·²è§¦å') |
| | | this.addSystemLog('æå¨è§¦ååºæ¥ååº') |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .safety-monitoring { |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | min-height: calc(100vh - 84px); |
| | | } |
| | | |
| | | .monitoring-card, .control-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .monitoring-section { |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .monitoring-section h3 { |
| | | color: #303133; |
| | | margin-bottom: 15px; |
| | | padding-bottom: 8px; |
| | | border-bottom: 2px solid #409EFF; |
| | | } |
| | | |
| | | .sensor-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); |
| | | gap: 15px; |
| | | } |
| | | |
| | | .sensor-item { |
| | | background: #fff; |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 8px; |
| | | padding: 15px; |
| | | box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| | | } |
| | | |
| | | .sensor-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .sensor-data .data-item { |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .sensor-data .data-item span { |
| | | display: block; |
| | | margin-bottom: 5px; |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .chart-section { |
| | | margin-top: 30px; |
| | | } |
| | | |
| | | .chart-section h3 { |
| | | color: #303133; |
| | | margin-bottom: 15px; |
| | | padding-bottom: 8px; |
| | | border-bottom: 2px solid #409EFF; |
| | | } |
| | | |
| | | .chart-container { |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .chart { |
| | | width: 100%; |
| | | height: 400px; |
| | | } |
| | | |
| | | .control-section { |
| | | margin-bottom: 25px; |
| | | } |
| | | .control-section1 { |
| | | display: flex; |
| | | } |
| | | |
| | | .control-section h4 { |
| | | color: #303133; |
| | | margin-bottom: 15px; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .status-grid { |
| | | display: grid; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .status-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | padding: 10px; |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | } |
| | | |
| | | .status-indicator { |
| | | width: 20px; |
| | | height: 20px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .status-indicator.active { |
| | | color: #67C23A; |
| | | } |
| | | |
| | | .status-indicator.standby { |
| | | color: #909399; |
| | | } |
| | | |
| | | .log-container { |
| | | max-height: 200px; |
| | | overflow-y: auto; |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | padding: 10px; |
| | | } |
| | | |
| | | .log-item { |
| | | display: flex; |
| | | gap: 10px; |
| | | margin-bottom: 8px; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .log-time { |
| | | color: #909399; |
| | | min-width: 60px; |
| | | } |
| | | |
| | | .log-content { |
| | | color: #606266; |
| | | } |
| | | |
| | | /* æ³æ¼é¢è¦å¼¹çªæ ·å¼ */ |
| | | .leak-warning-dialog { |
| | | background: #fff5f5; |
| | | } |
| | | |
| | | .warning-content { |
| | | text-align: center; |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .warning-icon { |
| | | font-size: 60px; |
| | | color: #F56C6C; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .warning-text h3 { |
| | | color: #F56C6C; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .warning-text p { |
| | | margin: 8px 0; |
| | | color: #606266; |
| | | } |
| | | |
| | | /* åºåé¾è¯¦æ
æ ·å¼ */ |
| | | .blockchain-details { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .sensor-data-section, .action-log-section, .blockchain-info { |
| | | margin-top: 25px; |
| | | } |
| | | |
| | | .sensor-data-section h4, .action-log-section h4, .blockchain-info h4 { |
| | | color: #303133; |
| | | margin-bottom: 15px; |
| | | padding-bottom: 8px; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | } |
| | | |
| | | /* ååºå¼è®¾è®¡ */ |
| | | @media (max-width: 1200px) { |
| | | .sensor-grid { |
| | | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .safety-monitoring { |
| | | padding: 10px; |
| | | } |
| | | |
| | | .sensor-grid { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | |
| | | .chart { |
| | | height: 300px; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ£éªåï¼" prop="checkName"> |
| | | <el-input v-model="form.checkName" placeholder="请è¾å
¥" clearable/> |
| | | |
| | | <el-select v-model="form.checkName" placeholder="è¯·éæ©" clearable> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" |
| | | :value="item.nickName"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | height="400" |
| | | > |
| | | <template #slot="{ row }"> |
| | | <el-input v-model="row.testValue" clearable/> |
| | | </template> |
| | | </PIMTable> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | |
| | | import {getOptions} from "@/api/procurementManagement/procurementLedger.js"; |
| | | import {productTreeList} from "@/api/basicData/product.js"; |
| | | import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import {userListNoPage} from "@/api/system/user.js"; |
| | | import {qualityInspectDetailByProductId} from "@/api/qualityManagement/metricMaintenance.js"; |
| | | import {qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | |
| | |
| | | const { form, rules } = toRefs(data); |
| | | const supplierList = ref([]); |
| | | const productOptions = ref([]); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ææ ", |
| | | prop: "parameterItem", |
| | | }, |
| | | { |
| | | label: "åä½", |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "æ åå¼", |
| | | prop: "standardValue", |
| | | }, |
| | | { |
| | | label: "å
æ§å¼", |
| | | prop: "controlValue", |
| | | }, |
| | | { |
| | | label: "æ£éªå¼", |
| | | prop: "testValue", |
| | | dataType: 'slot', |
| | | slot: 'slot', |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const userList = ref([]); |
| | | const currentProductId = ref(0); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | getOptions().then((res) => { |
| | | supplierList.value = res.data; |
| | | }); |
| | | let userLists = await userListNoPage(); |
| | | userList.value = userLists.data; |
| | | form.value = {} |
| | | getProductOptions(); |
| | | if (operationType.value === 'edit') { |
| | | form.value = {...row} |
| | | currentProductId.value = row.productId || 0 |
| | | getQualityInspectParamList(row.id) |
| | | } |
| | | } |
| | | const getProductOptions = () => { |
| | |
| | | }); |
| | | }; |
| | | const getModels = (value) => { |
| | | currentProductId.value = value |
| | | form.value.productName = findNodeById(productOptions.value, value); |
| | | if (currentProductId) { |
| | | getList(); |
| | | } |
| | | }; |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | form.value.inspectType = 2 |
| | | if (operationType.value === "add") { |
| | | tableData.value.forEach((item) => { |
| | | delete item.id |
| | | }) |
| | | } |
| | | const data = {...form.value, qualityInspectParams: tableData.value} |
| | | if (operationType.value === "add") { |
| | | qualityInspectAdd(form.value).then(res => { |
| | | qualityInspectAdd(data).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | } else { |
| | | qualityInspectUpdate(form.value).then(res => { |
| | | qualityInspectUpdate(data).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | |
| | | } |
| | | }) |
| | | } |
| | | const getList = () => { |
| | | qualityInspectDetailByProductId(currentProductId.value).then(res => { |
| | | tableData.value = res.data; |
| | | }) |
| | | } |
| | | const getQualityInspectParamList = (id) => { |
| | | qualityInspectParamInfo(id).then(res => { |
| | | tableData.value = res.data; |
| | | }) |
| | | } |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | |
| | | <InspectionFormDia ref="inspectionFormDia" @close="handleQuery"></InspectionFormDia> |
| | | <FormDia ref="formDia" @close="handleQuery"></FormDia> |
| | | <files-dia ref="filesDia" @close="handleQuery"></files-dia> |
| | | <el-dialog v-model="dialogFormVisible" title="ç¼è¾æ£éªå" width="30%" |
| | | @close="closeDia"> |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-form-item label="æ£éªåï¼" prop="checkName"> |
| | | <el-select v-model="form.checkName" placeholder="è¯·éæ©" clearable> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" |
| | | :value="item.nickName"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import InspectionFormDia from "@/views/qualityManagement/finalInspection/components/inspectionFormDia.vue"; |
| | | import FormDia from "@/views/qualityManagement/finalInspection/components/formDia.vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import {qualityInspectDel, qualityInspectListPage} from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import { |
| | | downloadQualityInspect, |
| | | qualityInspectDel, |
| | | qualityInspectListPage, qualityInspectUpdate, |
| | | submitQualityInspect |
| | | } from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import FilesDia from "@/views/qualityManagement/finalInspection/components/filesDia.vue"; |
| | | import dayjs from "dayjs"; |
| | | import {userListNoPage} from "@/api/system/user.js"; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | |
| | | entryDateStart: dayjs().format("YYYY-MM-DD"), |
| | | entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"), |
| | | }, |
| | | rules: { |
| | | checkName: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | |
| | | } |
| | | }, |
| | | }, |
| | | { |
| | | label: "æäº¤ç¶æ", |
| | | prop: "inspectState", |
| | | formatData: (params) => { |
| | | if (params) { |
| | | return "å·²æäº¤"; |
| | | } else { |
| | | return "æªæäº¤"; |
| | | } |
| | | }, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 190, |
| | | width: 280, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "æ°å¢æ£éªè®°å½", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openInspectionForm("edit", row); |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1; |
| | | } |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | |
| | | openFilesFormDia(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "æäº¤", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | submit(row.id); |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1; |
| | | } |
| | | }, |
| | | { |
| | | name: "åé
æ£éªå", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | if (!row.checkName) { |
| | | open(row) |
| | | } else { |
| | | proxy.$modal.msgError("æ£éªåå·²åå¨"); |
| | | } |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1 || row.checkName; |
| | | } |
| | | }, |
| | | { |
| | | name: "ä¸è½½", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | downLoadFile(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const currentRow = ref(null) |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | |
| | | const filesDia = ref() |
| | | const inspectionFormDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | const userList = ref([]); |
| | | const form = ref({ |
| | | checkName: "" |
| | | }); |
| | | const dialogFormVisible = ref(false); |
| | | |
| | | const changeDaterange = (value) => { |
| | | searchForm.value.entryDateStart = undefined; |
| | |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // æä»· |
| | | const submit = async (id) => { |
| | | const res = await submitQualityInspect({id: id}) |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | getList(); |
| | | } |
| | | } |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | if (currentRow.value) { |
| | | const data = { |
| | | ...form.value, |
| | | id: currentRow.value.id |
| | | } |
| | | qualityInspectUpdate(data).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }) |
| | | } |
| | | }; |
| | | |
| | | const open = async (row) => { |
| | | let userLists = await userListNoPage(); |
| | | userList.value = userLists.data; |
| | | currentRow.value = row |
| | | dialogFormVisible.value = true |
| | | } |
| | | |
| | | const downLoadFile = (row) => { |
| | | downloadQualityInspect({ id: row.id }).then((blobData) => { |
| | | const blob = new Blob([blobData], { |
| | | type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
| | | }) |
| | | const downloadUrl = window.URL.createObjectURL(blob) |
| | | |
| | | const link = document.createElement('a') |
| | | link.href = downloadUrl |
| | | link.download = 'åæææ£éªæ¥å.docx' |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | |
| | | document.body.removeChild(link) |
| | | window.URL.revokeObjectURL(downloadUrl) |
| | | }) |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <filePreview ref="filePreviewRef" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js"; |
| | | import {Search} from "@element-plus/icons-vue"; |
| | | import { |
| | | qualityInspectParamDel, |
| | | qualityInspectParamInfo, |
| | | qualityInspectParamUpdate |
| | | } from "@/api/qualityManagement/qualityInspectParam.js"; |
| | | import filePreview from '@/components/filePreview/index.vue' |
| | | import {ElMessageBox} from "element-plus"; |
| | | import {getToken} from "@/utils/auth.js"; |
| | | import { |
| | |
| | | const dialogFormVisible = ref(false); |
| | | const currentId = ref('') |
| | | const selectedRows = ref([]); |
| | | const filePreviewRef = ref() |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "æä»¶åç§°", |
| | |
| | | clickFun: (row) => { |
| | | downLoadFile(row); |
| | | }, |
| | | } |
| | | }, |
| | | { |
| | | name: "é¢è§", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | lookFile(row); |
| | | }, |
| | | } |
| | | ], |
| | | }, |
| | | ]); |
| | |
| | | function handleUploadError() { |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | } |
| | | // é¢è§éä»¶ |
| | | const lookFile = (row) => { |
| | | filePreviewRef.value.open(row.url) |
| | | } |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ£éªåï¼" prop="checkName"> |
| | | <el-input v-model="form.checkName" placeholder="请è¾å
¥" clearable/> |
| | | |
| | | <el-select v-model="form.checkName" placeholder="è¯·éæ©" clearable> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" |
| | | :value="item.nickName"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | height="400" |
| | | > |
| | | <template #slot="{ row }"> |
| | | <el-input v-model="row.testValue" clearable/> |
| | | </template> |
| | | </PIMTable> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | |
| | | import {getOptions} from "@/api/procurementManagement/procurementLedger.js"; |
| | | import {productTreeList} from "@/api/basicData/product.js"; |
| | | import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import {qualityInspectDetailByProductId} from "@/api/qualityManagement/metricMaintenance.js"; |
| | | import {userListNoPage} from "@/api/system/user.js"; |
| | | import {qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | |
| | |
| | | checkResult: [{ required: true, message: "请è¾å
¥", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const userList = ref([]); |
| | | const { form, rules } = toRefs(data); |
| | | const supplierList = ref([]); |
| | | const productOptions = ref([]); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ææ ", |
| | | prop: "parameterItem", |
| | | }, |
| | | { |
| | | label: "åä½", |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "æ åå¼", |
| | | prop: "standardValue", |
| | | }, |
| | | { |
| | | label: "å
æ§å¼", |
| | | prop: "controlValue", |
| | | }, |
| | | { |
| | | label: "æ£éªå¼", |
| | | prop: "testValue", |
| | | dataType: 'slot', |
| | | slot: 'slot', |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const currentProductId = ref(0); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | getOptions().then((res) => { |
| | | supplierList.value = res.data; |
| | | }); |
| | | getProductOptions(); |
| | | if (operationType.value === 'edit') { |
| | | form.value = {...row} |
| | | } |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | getOptions().then((res) => { |
| | | supplierList.value = res.data; |
| | | }); |
| | | let userLists = await userListNoPage(); |
| | | userList.value = userLists.data; |
| | | form.value = {} |
| | | getProductOptions(); |
| | | if (operationType.value === 'edit') { |
| | | form.value = {...row} |
| | | currentProductId.value = row.productId || 0 |
| | | getQualityInspectParamList(row.id) |
| | | } |
| | | } |
| | | const getProductOptions = () => { |
| | | productTreeList().then((res) => { |
| | |
| | | }); |
| | | }; |
| | | const getModels = (value) => { |
| | | currentProductId.value = value |
| | | form.value.productName = findNodeById(productOptions.value, value); |
| | | if (currentProductId) { |
| | | getList(); |
| | | } |
| | | }; |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | form.value.inspectType = 1 |
| | | if (operationType.value === "add") { |
| | | tableData.value.forEach((item) => { |
| | | delete item.id |
| | | }) |
| | | } |
| | | const data = {...form.value, qualityInspectParams: tableData.value} |
| | | if (operationType.value === "add") { |
| | | qualityInspectAdd(form.value).then(res => { |
| | | qualityInspectAdd(data).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | } else { |
| | | qualityInspectUpdate(form.value).then(res => { |
| | | qualityInspectUpdate(data).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | |
| | | } |
| | | }) |
| | | } |
| | | const getList = () => { |
| | | qualityInspectDetailByProductId(currentProductId.value).then(res => { |
| | | tableData.value = res.data; |
| | | }) |
| | | } |
| | | const getQualityInspectParamList = (id) => { |
| | | qualityInspectParamInfo(id).then(res => { |
| | | tableData.value = res.data; |
| | | }) |
| | | } |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | |
| | | <InspectionFormDia ref="inspectionFormDia" @close="handleQuery"></InspectionFormDia> |
| | | <FormDia ref="formDia" @close="handleQuery"></FormDia> |
| | | <files-dia ref="filesDia" @close="handleQuery"></files-dia> |
| | | <el-dialog v-model="dialogFormVisible" title="ç¼è¾æ£éªå" width="30%" |
| | | @close="closeDia"> |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-form-item label="æ£éªåï¼" prop="checkName"> |
| | | <el-select v-model="form.checkName" placeholder="è¯·éæ©" clearable> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" |
| | | :value="item.nickName"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import InspectionFormDia from "@/views/qualityManagement/processInspection/components/inspectionFormDia.vue"; |
| | | import FormDia from "@/views/qualityManagement/processInspection/components/formDia.vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import {qualityInspectDel, qualityInspectListPage} from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import { |
| | | downloadQualityInspect, |
| | | qualityInspectDel, |
| | | qualityInspectListPage, qualityInspectUpdate, |
| | | submitQualityInspect |
| | | } from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import FilesDia from "@/views/qualityManagement/processInspection/components/filesDia.vue"; |
| | | import dayjs from "dayjs"; |
| | | import {userListNoPage} from "@/api/system/user.js"; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | |
| | | entryDateStart: dayjs().format("YYYY-MM-DD"), |
| | | entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"), |
| | | }, |
| | | rules: { |
| | | checkName: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | |
| | | } |
| | | }, |
| | | }, |
| | | { |
| | | label: "æäº¤ç¶æ", |
| | | prop: "inspectState", |
| | | formatData: (params) => { |
| | | if (params) { |
| | | return "å·²æäº¤"; |
| | | } else { |
| | | return "æªæäº¤"; |
| | | } |
| | | }, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 190, |
| | | width: 280, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "æ°å¢æ£éªè®°å½", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openInspectionForm("edit", row); |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1; |
| | | } |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | |
| | | openFilesFormDia(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "æäº¤", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | submit(row.id); |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1; |
| | | } |
| | | }, |
| | | { |
| | | name: "åé
æ£éªå", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | if (!row.checkName) { |
| | | open(row) |
| | | } else { |
| | | proxy.$modal.msgError("æ£éªåå·²åå¨"); |
| | | } |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1 || row.checkName; |
| | | } |
| | | }, |
| | | { |
| | | name: "ä¸è½½", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | downLoadFile(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const userList = ref([]); |
| | | const currentRow = ref(null) |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const dialogFormVisible = ref(false); |
| | | const form = ref({ |
| | | checkName: "" |
| | | }); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | |
| | | filesDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | // æä»· |
| | | const submit = async (id) => { |
| | | const res = await submitQualityInspect({id: id}) |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | getList(); |
| | | } |
| | | } |
| | | const open = async (row) => { |
| | | let userLists = await userListNoPage(); |
| | | userList.value = userLists.data; |
| | | currentRow.value = row |
| | | dialogFormVisible.value = true |
| | | } |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | const submitForm = () => { |
| | | if (currentRow.value) { |
| | | const data = { |
| | | ...form.value, |
| | | id: currentRow.value.id |
| | | } |
| | | qualityInspectUpdate(data).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }) |
| | | } |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | const downLoadFile = (row) => { |
| | | downloadQualityInspect({ id: row.id }).then((blobData) => { |
| | | const blob = new Blob([blobData], { |
| | | type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
| | | }) |
| | | const downloadUrl = window.URL.createObjectURL(blob) |
| | | |
| | | const link = document.createElement('a') |
| | | link.href = downloadUrl |
| | | link.download = 'è¿ç¨æ£éªæ¥å.docx' |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | |
| | | document.body.removeChild(link) |
| | | window.URL.revokeObjectURL(downloadUrl) |
| | | }) |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <filePreview ref="filePreviewRef" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js"; |
| | | import {Search} from "@element-plus/icons-vue"; |
| | | import { |
| | | qualityInspectParamDel, |
| | | qualityInspectParamInfo, |
| | | qualityInspectParamUpdate |
| | | } from "@/api/qualityManagement/qualityInspectParam.js"; |
| | | import filePreview from '@/components/filePreview/index.vue' |
| | | import {ElMessageBox} from "element-plus"; |
| | | import {getToken} from "@/utils/auth.js"; |
| | | import { |
| | |
| | | clickFun: (row) => { |
| | | downLoadFile(row); |
| | | }, |
| | | } |
| | | }, |
| | | { |
| | | name: "é¢è§", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | lookFile(row); |
| | | }, |
| | | } |
| | | ], |
| | | }, |
| | | ]); |
| | |
| | | const tableData = ref([]); |
| | | const fileList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const filePreviewRef = ref() |
| | | const headers = ref({ |
| | | Authorization: "Bearer " + getToken(), |
| | | }); |
| | |
| | | const downLoadFile = (row) => { |
| | | proxy.$download.name(row.url); |
| | | } |
| | | // é¢è§éä»¶ |
| | | const lookFile = (row) => { |
| | | filePreviewRef.value.open(row.url) |
| | | } |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <div style="margin-bottom: 10px;text-align: right"> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | <!-- <div style="margin-bottom: 10px;text-align: right">--> |
| | | <!-- <el-button type="danger" plain @click="handleDelete">å é¤</el-button>--> |
| | | <!-- </div>--> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | height="400" |
| | | > |
| | | <template #slot="{ row }"> |
| | |
| | | getOptions().then((res) => { |
| | | supplierList.value = res.data; |
| | | }); |
| | | form.value = {} |
| | | getProductOptions(); |
| | | if (operationType.value === 'edit') { |
| | | form.value = {...row} |
| | |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | form.value.inspectType = 0 |
| | | if (operationType.value === "add") { |
| | | tableData.value.forEach((item) => { |
| | | delete item.id |
| | | }) |
| | | } |
| | | const data = {...form.value, qualityInspectParams: tableData.value} |
| | | if (operationType.value === "add") { |
| | | qualityInspectAdd(data).then(res => { |
| | |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | qualityInspectParamDel(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | qualityInspectDetailByProductId(currentProductId.value).then(res => { |
| | |
| | | <InspectionFormDia ref="inspectionFormDia" @close="handleQuery"></InspectionFormDia> |
| | | <FormDia ref="formDia" @close="handleQuery"></FormDia> |
| | | <files-dia ref="filesDia" @close="handleQuery"></files-dia> |
| | | <el-dialog v-model="dialogFormVisible" title="ç¼è¾æ£éªå" width="70%" |
| | | <el-dialog v-model="dialogFormVisible" title="ç¼è¾æ£éªå" width="30%" |
| | | @close="closeDia"> |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-form-item label="æ£éªåï¼" prop="checkName"> |
| | |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 250, |
| | | width: 280, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1; |
| | | } |
| | | }, |
| | | { |
| | | name: "éä»¶", |
| | |
| | | clickFun: (row) => { |
| | | submit(row.id); |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1; |
| | | } |
| | | }, |
| | | { |
| | | name: "åé
æ£éªå", |
| | |
| | | proxy.$modal.msgError("æ£éªåå·²åå¨"); |
| | | } |
| | | }, |
| | | disabled: (row) => { |
| | | return row.inspectState == 1 || row.checkName; |
| | | } |
| | | }, |
| | | { |
| | | name: "ä¸è½½", |
| | |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | // æå¼æ°å¢æ£éªå¼¹æ¡ |
| | | const openInspectionForm = (type, row) => { |
| | | nextTick(() => { |
| | | inspectionFormDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | // æå¼éä»¶å¼¹æ¡ |
| | |
| | | } |
| | | |
| | | const downLoadFile = (row) => { |
| | | downloadQualityInspect({id: row.id}).then(res => { |
| | | // å建 blob 对象 |
| | | const blob = new Blob([res.data], {type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'}) |
| | | downloadQualityInspect({ id: row.id }).then((blobData) => { |
| | | const blob = new Blob([blobData], { |
| | | type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
| | | }) |
| | | const downloadUrl = window.URL.createObjectURL(blob) |
| | | |
| | | // åå»ºä¸´æ¶ <a> æ ç¾è¿è¡ä¸è½½ |
| | | const link = document.createElement('a') |
| | | link.href = downloadUrl |
| | | link.download = 'æ£éªæ¥å.docx' // è¿éåå端ä¸è´ |
| | | link.download = 'åæææ£éªæ¥å.docx' |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | |
| | | // æ¸
ç |
| | | document.body.removeChild(link) |
| | | window.URL.revokeObjectURL(downloadUrl) |
| | | }) |
| | |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | :isShowSummary="true" |
| | | :summaryMethod="summaryMethod" |
| | | @pagination="changePage" |
| | | ></PIMTable> |
| | | </div> |
| | |
| | | import { usePaginationApi } from "@/hooks/usePaginationApi"; |
| | | import { getPurchaseList } from "@/api/procurementManagement/projectProfit"; |
| | | import { onMounted } from "vue"; |
| | | import { summarizeTable } from "@/utils/summarizeTable"; |
| | | |
| | | defineOptions({ |
| | | name: "项ç®å©æ¶¦", |
| | |
| | | onCurrentChange(page); |
| | | }; |
| | | |
| | | // åè®¡æ¹æ³ |
| | | const summaryMethod = (param) => { |
| | | return summarizeTable( |
| | | param, |
| | | ['contractAmount', 'purchaseAmount', 'balance', 'balanceAmount', 'balanceRatio'], |
| | | ); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | |
| | | </div> |
| | | <div class="table_list"> |
| | | <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" |
| | | :row-key="(row) => row.id" show-summary :summary-method="summarizeMainTable" height="calc(100vh - 18.5em)"> |
| | | :row-key="(row) => row.id" show-summary :summary-method="summarizeMainTable" height="calc(100vh - 18.5em)" stripe> |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="éå®ååå·" prop="salesContractNo" show-overflow-tooltip width="180" /> |
| | |
| | | <el-table-column label="å½å
¥äºº" prop="invoicePerson" show-overflow-tooltip /> |
| | | <el-table-column label="å½å
¥æ¥æ" prop="createTime" show-overflow-tooltip :formatter="formatDate" width="180" /> |
| | | <el-table-column label="å¼ç¥¨æ¥æ" prop="invoiceDate" show-overflow-tooltip width="120" /> |
| | | <el-table-column label="å票" prop="invoiceFileName" width="120" align="center" show-overflow-tooltip> |
| | | <el-table-column label="å票" prop="invoiceFileName" width="120" align="center" show-overflow-tooltip fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button v-if="scope.row.invoiceFileName" text bg type="primary" |
| | | @click="handleFile(scope.row.commonFiles)"> |
| | |
| | | function handleBeforeUpload(file) { |
| | | console.log("file", file); |
| | | // æ ¡æ£æä»¶å¤§å° |
| | | if (file.size > 1024 * 1024 * 10) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶
è¿10MB!"); |
| | | if (file.size > 1024 * 1024 * 50) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶
è¿50MB!"); |
| | | return false; |
| | | } |
| | | // 夿æä»¶æ ¼å¼æ¯å¦ç¬¦å |
| | |
| | | @change="handleQuery" |
| | | /> |
| | | </el-form-item> |
| | | <br/> |
| | | <el-form-item label="ååå½å
¥æ¥æ"> |
| | | <el-date-picker style="width: 240px" v-model="searchForm.commonDate" value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" type="daterange" start-placeholder="å¼å§æ¶é´" end-placeholder="ç»ææ¶é´" clearable |
| | | @change="changeDateRange" @clear="clearRange" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery"> æç´¢ </el-button> |
| | | <el-button @click="resetForm"> éç½® </el-button> |
| | |
| | | :summary-method="summarizeMainTable" |
| | | @expand-change="expandChange" |
| | | @selection-change="handleSelectionChange" |
| | | stripe |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column type="expand"> |
| | |
| | | border |
| | | show-summary |
| | | :summary-method="summarizeChildrenTable" |
| | | stripe |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="ååå½å
¥æ¥æ" prop="entryDate" width="120" /> |
| | | <el-table-column |
| | | label="éå®ååå·" |
| | | prop="salesContractNo" |
| | |
| | | const { form, rules } = toRefs(data); |
| | | const { form: searchForm, resetForm } = useFormData(data.searchForm); |
| | | |
| | | |
| | | const changeDateRange = (date) => { |
| | | if (date) { |
| | | searchForm.entryDateStart = date[0]; |
| | | searchForm.entryDateEnd = date[1]; |
| | | getList(); |
| | | } |
| | | }; |
| | | |
| | | const clearRange = () => { |
| | | searchForm.commonDate = []; |
| | | searchForm.entryDateStart = undefined; |
| | | searchForm.entryDateEnd = undefined; |
| | | getList(); |
| | | }; |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | if (cellValue == 0) { |
| | | return parseFloat(cellValue).toFixed(2); |
| | |
| | | getSalesLedgerWithProducts({ id: selectedRows.value[0].id }).then((res) => { |
| | | form.value = { ...res }; |
| | | form.value.createTime = dayjs().format("YYYY-MM-DD"); |
| | | form.value.issueDate = dayjs().format("YYYY-MM-DD"); |
| | | form.value.createUer = userStore.nickName; |
| | | productData.value = form.value.productData.map((item) => { |
| | | return item; |
| | |
| | | @change="handleQuery" |
| | | /> |
| | | </el-form-item> |
| | | <br/> |
| | | <el-form-item label="å¼ç¥¨æ¥æ"> |
| | | <el-date-picker style="width: 240px" v-model="searchForm.commonDate" value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" type="daterange" start-placeholder="å¼å§æ¶é´" end-placeholder="ç»ææ¶é´" clearable |
| | | @change="changeDateRange" @clear="clearRange" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery"> æç´¢ </el-button> |
| | | </el-form-item> |
| | |
| | | :summary-method="summarizeMainTable" |
| | | :expand-row-keys="expandedRowKeys" |
| | | @expand-change="expandChange" |
| | | |
| | | stripe |
| | | height="calc(100vh - 21.5em)" |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | |
| | | border |
| | | show-summary |
| | | :summary-method="summarizeChildrenTable" |
| | | stripe |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column |
| | | label="å¼ç¥¨æ¥æ" |
| | | prop="invoiceDate" |
| | | show-overflow-tooltip |
| | | width="240" |
| | | /> |
| | | <el-table-column |
| | | label="éå®ååå·" |
| | | prop="salesContractNo" |
| | |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | return parseFloat(cellValue).toFixed(2); |
| | | }; |
| | | |
| | | const changeDateRange = (date) => { |
| | | if (date) { |
| | | searchForm.invoiceDateStart = date[0]; |
| | | searchForm.invoiceDateEnd = date[1]; |
| | | getList(); |
| | | } |
| | | }; |
| | | |
| | | const clearRange = () => { |
| | | searchForm.commonDate = []; |
| | | searchForm.invoiceDateStart = undefined; |
| | | searchForm.invoiceDateEnd = undefined; |
| | | getList(); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | |
| | | return; |
| | | } |
| | | if (selectedRows.value[0].noReceiptAmount == 0) { |
| | | proxy.$modal.warning("æ éå忬¾"); |
| | | proxy.$modal.msgWarning("æ éå忬¾"); |
| | | return; |
| | | } |
| | | invoiceInfo({ id: selectedRows.value[0].id }).then((res) => { |
| | |
| | | :summary-method="summarizeMainTable" |
| | | @row-click="rowClickMethod" |
| | | height="calc(100vh - 18.5em)" |
| | | stripe |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | |
| | | :data="receiptRecord" |
| | | border |
| | | :row-key="(row) => row.id" |
| | | stripe |
| | | show-summary |
| | | :summary-method="summarizeMainTable1" |
| | | height="calc(100vh - 18.5em)" |
| | |
| | | <template> |
| | | <el-dialog v-model="dialogVisible" title="éä»¶" width="40%" :before-close="handleClose"> |
| | | <el-table :data="tableData" border height="40vh"> |
| | | <el-table :data="tableData" border height="40vh" stripe> |
| | | <el-table-column label="éä»¶åç§°" prop="name" min-width="400" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="æä½" width="100" align="center"> |
| | | <template #default="scope"> |
| | |
| | | </div> |
| | | <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" :row-key="(row) => row.id" show-summary style="width: 100%" |
| | | :summary-method="summarizeMainTable" @expand-change="expandChange" height="calc(100vh - 18.5em)"> |
| | | :summary-method="summarizeMainTable" @expand-change="expandChange" height="calc(100vh - 18.5em)" stripe> |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column type="expand"> |
| | | <template #default="props"> |
| | | <el-table :data="props.row.children" border show-summary :summary-method="summarizeChildrenTable"> |
| | | <el-table :data="props.row.children" border show-summary :summary-method="summarizeChildrenTable" stripe> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="产å大类" prop="productCategory" /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> |
| | |
| | | <el-table-column label="仿¬¾æ¹å¼" prop="paymentMethod" show-overflow-tooltip /> |
| | | <el-table-column label="ååéé¢(å
)" prop="contractAmount" width="220" show-overflow-tooltip |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="å·²å¼ç¥¨éé¢(å
)" prop="invoiceTotal" width="220" show-overflow-tooltip |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="æªå¼ç¥¨éé¢(å
)" prop="noInvoiceAmountTotal" width="220" show-overflow-tooltip |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="忬¾éé¢(å
)" prop="receiptPaymentAmountTotal" width="220" show-overflow-tooltip |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="å¾
忬¾éé¢(å
)" prop="noReceiptAmount" width="220" show-overflow-tooltip |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="å½å
¥äºº" prop="entryPersonName" width="100" show-overflow-tooltip /> |
| | | <el-table-column label="å½å
¥æ¥æ" prop="entryDate" width="120" show-overflow-tooltip /> |
| | | <el-table-column label="ç¾è®¢æ¥æ" prop="executionDate" width="120" show-overflow-tooltip /> |
| | |
| | | <el-button v-if="operationType !== 'view'" plain type="danger" @click="deleteProduct" >å é¤</el-button> |
| | | </el-form-item> |
| | | </el-row> |
| | | <el-table :data="productData" border @selection-change="productSelected" show-summary |
| | | <el-table :data="productData" border @selection-change="productSelected" show-summary stripe |
| | | :summary-method="summarizeMainTable"> |
| | | <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | |
| | | "contractAmount", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | 'invoiceTotal', |
| | | 'noInvoiceAmountTotal', |
| | | 'receiptPaymentAmountTotal', |
| | | 'noReceiptAmount', |
| | | ]); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | |
| | | <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
| | | </el-row>
|
| | |
|
| | | <el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange">
|
| | | <el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange" stripe>
|
| | | <el-table-column type="selection" width="55" align="center" />
|
| | | <el-table-column label="忰䏻é®" align="center" prop="configId" />
|
| | | <el-table-column label="åæ°åç§°" align="center" prop="configName" :show-overflow-tooltip="true" />
|
| | |
| | | <template>
|
| | | <div class="app-container">
|
| | | <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
|
| | | <el-form-item label="é¨é¨åç§°" prop="deptName">
|
| | | <el-form-item label="å
¬å¸åç§°" prop="deptName">
|
| | | <el-input
|
| | | v-model="queryParams.deptName"
|
| | | placeholder="请è¾å
¥é¨é¨åç§°"
|
| | | placeholder="请è¾å
¥å
¬å¸åç§°"
|
| | | clearable
|
| | | style="width: 200px"
|
| | | @keyup.enter="handleQuery"
|
| | | />
|
| | | </el-form-item>
|
| | | <el-form-item label="ç¶æ" prop="status">
|
| | | <el-select v-model="queryParams.status" placeholder="é¨é¨ç¶æ" clearable style="width: 200px">
|
| | | <el-select v-model="queryParams.status" placeholder="å
¬å¸ç¶æ" clearable style="width: 200px">
|
| | | <el-option
|
| | | v-for="dict in sys_normal_disable"
|
| | | :key="dict.value"
|
| | |
| | | row-key="deptId"
|
| | | :default-expand-all="isExpandAll"
|
| | | :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
| | | stripe
|
| | | >
|
| | | <el-table-column prop="deptName" label="é¨é¨åç§°" width="260"></el-table-column>
|
| | | <el-table-column prop="deptName" label="å
¬å¸åç§°" width="260"></el-table-column>
|
| | | <el-table-column prop="orderNum" label="æåº" width="200"></el-table-column>
|
| | | <el-table-column prop="status" label="ç¶æ" width="100">
|
| | | <template #default="scope">
|
| | |
| | | </el-table-column>
|
| | | </el-table>
|
| | |
|
| | | <!-- æ·»å æä¿®æ¹é¨é¨å¯¹è¯æ¡ -->
|
| | | <!-- æ·»å æä¿®æ¹å
¬å¸å¯¹è¯æ¡ -->
|
| | | <el-dialog :title="title" v-model="open" width="600px" append-to-body>
|
| | | <el-form ref="deptRef" :model="form" :rules="rules" label-width="80px">
|
| | | <el-row>
|
| | | <el-col :span="24" v-if="form.parentId !== 0">
|
| | | <el-form-item label="ä¸çº§é¨é¨" prop="parentId">
|
| | | <el-form-item label="ä¸çº§å
¬å¸" prop="parentId">
|
| | | <el-tree-select
|
| | | v-model="form.parentId"
|
| | | :data="deptOptions"
|
| | | :props="{ value: 'deptId', label: 'deptName', children: 'children' }"
|
| | | value-key="deptId"
|
| | | placeholder="éæ©ä¸çº§é¨é¨"
|
| | | placeholder="éæ©ä¸çº§å
¬å¸"
|
| | | check-strictly
|
| | | />
|
| | | </el-form-item>
|
| | | </el-col>
|
| | | <el-col :span="12">
|
| | | <el-form-item label="é¨é¨åç§°" prop="deptName">
|
| | | <el-input v-model="form.deptName" placeholder="请è¾å
¥é¨é¨åç§°" />
|
| | | <el-form-item label="å
¬å¸åç§°" prop="deptName">
|
| | | <el-input v-model="form.deptName" placeholder="请è¾å
¥å
¬å¸åç§°" />
|
| | | </el-form-item>
|
| | | </el-col>
|
| | | <el-col :span="12">
|
| | |
| | | </el-form-item>
|
| | | </el-col>
|
| | | <el-col :span="12">
|
| | | <el-form-item label="é¨é¨ç¶æ">
|
| | | <el-form-item label="å
¬å¸ç¶æ">
|
| | | <el-radio-group v-model="form.status">
|
| | | <el-radio
|
| | | v-for="dict in sys_normal_disable"
|
| | |
| | | </el-form-item>
|
| | | </el-col>
|
| | | <el-col :span="12">
|
| | | <el-form-item label="é¨é¨ç¼å·" prop="deptNick">
|
| | | <el-input v-model="form.deptNick" placeholder="请è¾å
¥é¨é¨ç¼å·" maxlength="50" />
|
| | | <el-form-item label="å
¬å¸ç¼å·" prop="deptNick">
|
| | | <el-input v-model="form.deptNick" placeholder="请è¾å
¥å
¬å¸ç¼å·" maxlength="50" />
|
| | | </el-form-item>
|
| | | </el-col>
|
| | | </el-row>
|
| | |
| | | status: undefined
|
| | | },
|
| | | rules: {
|
| | | parentId: [{ required: true, message: "ä¸çº§é¨é¨ä¸è½ä¸ºç©º", trigger: "blur" }],
|
| | | deptName: [{ required: true, message: "é¨é¨åç§°ä¸è½ä¸ºç©º", trigger: "blur" }],
|
| | | parentId: [{ required: true, message: "ä¸çº§å
¬å¸ä¸è½ä¸ºç©º", trigger: "blur" }],
|
| | | deptName: [{ required: true, message: "å
¬å¸åç§°ä¸è½ä¸ºç©º", trigger: "blur" }],
|
| | | orderNum: [{ required: true, message: "æ¾ç¤ºæåºä¸è½ä¸ºç©º", trigger: "blur" }],
|
| | | email: [{ type: "email", message: "请è¾å
¥æ£ç¡®çé®ç®±å°å", trigger: ["blur", "change"] }],
|
| | | phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请è¾å
¥æ£ç¡®çææºå·ç ", trigger: "blur" }],
|
| | | deptNick: [{ required: true, message: "é¨é¨ç¼å·ä¸è½ä¸ºç©º", trigger: "blur" }],
|
| | | deptNick: [{ required: true, message: "å
¬å¸ç¼å·ä¸è½ä¸ºç©º", trigger: "blur" }],
|
| | | },
|
| | | })
|
| | |
|
| | | const { queryParams, form, rules } = toRefs(data)
|
| | |
|
| | | /** æ¥è¯¢é¨é¨å表 */
|
| | | /** æ¥è¯¢å
¬å¸å表 */
|
| | | function getList() {
|
| | | loading.value = true
|
| | | listDept(queryParams.value).then(response => {
|
| | |
| | | form.value.parentId = row.deptId
|
| | | }
|
| | | open.value = true
|
| | | title.value = "æ·»å é¨é¨"
|
| | | title.value = "æ·»å å
¬å¸"
|
| | | }
|
| | |
|
| | | /** å±å¼/æå æä½ */
|
| | |
| | | getDept(row.deptId).then(response => {
|
| | | form.value = response.data
|
| | | open.value = true
|
| | | title.value = "ä¿®æ¹é¨é¨"
|
| | | title.value = "ä¿®æ¹å
¬å¸"
|
| | | })
|
| | | }
|
| | |
|
| | |
| | | <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
| | | </el-row>
|
| | |
|
| | | <el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
|
| | | <el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange" stripe>
|
| | | <el-table-column type="selection" width="55" align="center" />
|
| | | <el-table-column label="åå
¸ç¼ç " align="center" prop="dictCode" />
|
| | | <el-table-column label="åå
¸æ ç¾" align="center" prop="dictLabel">
|
| | |
| | | <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
| | | </el-row>
|
| | |
|
| | | <el-table v-loading="loading" :data="typeList" @selection-change="handleSelectionChange">
|
| | | <el-table v-loading="loading" :data="typeList" @selection-change="handleSelectionChange" stripe>
|
| | | <el-table-column type="selection" width="55" align="center" />
|
| | | <el-table-column label="åå
¸ç¼å·" align="center" prop="dictId" />
|
| | | <el-table-column label="åå
¸åç§°" align="center" prop="dictName" :show-overflow-tooltip="true"/>
|
| | |
| | | v-if="refreshTable"
|
| | | v-loading="loading"
|
| | | :data="menuList"
|
| | | stripe
|
| | | row-key="menuId"
|
| | | :default-expand-all="isExpandAll"
|
| | | :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
| | |
| | | <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
| | | </el-row>
|
| | |
|
| | | <el-table v-loading="loading" :data="noticeList" @selection-change="handleSelectionChange">
|
| | | <el-table v-loading="loading" :data="noticeList" @selection-change="handleSelectionChange" stripe>
|
| | | <el-table-column type="selection" width="55" align="center" />
|
| | | <el-table-column label="åºå·" align="center" prop="noticeId" width="100" />
|
| | | <el-table-column
|
| | |
| | | <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
| | | </el-row>
|
| | |
|
| | | <el-table v-loading="loading" :data="postList" @selection-change="handleSelectionChange">
|
| | | <el-table v-loading="loading" :data="postList" @selection-change="handleSelectionChange" stripe>
|
| | | <el-table-column type="selection" width="55" align="center" />
|
| | | <el-table-column label="å²ä½ç¼å·" align="center" prop="postId" />
|
| | | <el-table-column label="å²ä½ç¼ç " align="center" prop="postCode" />
|
| | |
| | | <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
| | | </el-row>
|
| | |
|
| | | <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
|
| | | <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange" stripe>
|
| | | <el-table-column type="selection" width="55" align="center" />
|
| | | <el-table-column label="ç¨æ·åç§°" prop="userName" :show-overflow-tooltip="true" />
|
| | | <el-table-column label="ç¨æ·æµç§°" prop="nickName" :show-overflow-tooltip="true" />
|
| | |
| | | </el-row>
|
| | |
|
| | | <!-- è¡¨æ ¼æ°æ® -->
|
| | | <el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
|
| | | <el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange" stripe>
|
| | | <el-table-column type="selection" width="55" align="center" />
|
| | | <el-table-column label="è§è²ç¼å·" prop="roleId" width="120" />
|
| | | <el-table-column label="è§è²åç§°" prop="roleName" :show-overflow-tooltip="true" width="150" />
|
| | |
| | | </el-form-item>
|
| | | </el-form>
|
| | | <el-row>
|
| | | <el-table @row-click="clickRow" ref="refTable" :data="userList" @selection-change="handleSelectionChange" height="260px">
|
| | | <el-table @row-click="clickRow" ref="refTable" :data="userList" @selection-change="handleSelectionChange" height="260px" stripe>
|
| | | <el-table-column type="selection" width="55"></el-table-column>
|
| | | <el-table-column label="ç¨æ·åç§°" prop="userName" :show-overflow-tooltip="true" />
|
| | | <el-table-column label="ç¨æ·æµç§°" prop="nickName" :show-overflow-tooltip="true" />
|
| | |
| | | </el-form>
|
| | |
|
| | | <h4 class="form-header h4">è§è²ä¿¡æ¯</h4>
|
| | | <el-table v-loading="loading" :row-key="getRowKey" @row-click="clickRow" ref="roleRef" @selection-change="handleSelectionChange" :data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)">
|
| | | <el-table v-loading="loading" :row-key="getRowKey" @row-click="clickRow" ref="roleRef" @selection-change="handleSelectionChange" :data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)" stripe>
|
| | | <el-table-column label="åºå·" width="55" type="index" align="center">
|
| | | <template #default="scope">
|
| | | <span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
|
| | |
| | | v-loading="loading"
|
| | | :data="userList"
|
| | | @selection-change="handleSelectionChange"
|
| | | stripe
|
| | | >
|
| | | <el-table-column type="selection" width="50" align="center" />
|
| | | <el-table-column
|
| | |
| | | <basic-info-form ref="basicInfo" :info="info" />
|
| | | </el-tab-pane>
|
| | | <el-tab-pane label="åæ®µä¿¡æ¯" name="columnInfo">
|
| | | <el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight">
|
| | | <el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight" stripe>
|
| | | <el-table-column label="åºå·" type="index" min-width="5%"/>
|
| | | <el-table-column
|
| | | label="åæ®µåå"
|
| | |
| | | </el-form-item>
|
| | | </el-form>
|
| | | <el-row>
|
| | | <el-table @row-click="clickRow" ref="table" :data="dbTableList" @selection-change="handleSelectionChange" height="260px">
|
| | | <el-table @row-click="clickRow" ref="table" :data="dbTableList" @selection-change="handleSelectionChange" height="260px" stripe>
|
| | | <el-table-column type="selection" width="55"></el-table-column>
|
| | | <el-table-column prop="tableName" label="表åç§°" :show-overflow-tooltip="true"></el-table-column>
|
| | | <el-table-column prop="tableComment" label="表æè¿°" :show-overflow-tooltip="true"></el-table-column>
|
| | |
| | | ref="genRef"
|
| | | v-loading="loading"
|
| | | :data="tableList"
|
| | | stripe
|
| | | @selection-change="handleSelectionChange"
|
| | | :default-sort="defaultSort"
|
| | | @sort-change="handleSortChange"
|