Merge remote-tracking branch 'origin/dev' into dev
| | |
| | | "logo": "logo/JSYNYLogo.png", |
| | | "favicon": "favicon/JSYNYico.ico" |
| | | }, |
| | | "CMNY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "åéè½æºä¿¡æ¯ç®¡çç³»ç»", |
| | | "VITE_BASE_API": "http://114.132.189.42:9088", |
| | | "VITE_JAVA_API": "http://114.132.189.42:9087" |
| | | }, |
| | | "screen": "screen/DHDCView.png", |
| | | "logo": "logo/CMNYLogo.png", |
| | | "favicon": "favicon/CMNYico.ico" |
| | | }, |
| | | "screen": "/src/assets/images/login-background.png", |
| | | "logo": "/src/assets/logo/logo.png", |
| | | "favicon": "/public/favicon.ico" |
| | |
| | | "jsencrypt": "3.3.2", |
| | | "nprogress": "0.2.0", |
| | | "pinia": "2.1.7", |
| | | "print-js": "^1.6.0", |
| | | "qrcode": "^1.5.4", |
| | | "sortablejs": "^1.15.6", |
| | | "splitpanes": "3.1.5", |
| | | "vue": "3.4.31", |
| | | "vue-cropper": "1.1.1", |
| | | "vue-easy-lightbox": "^1.19.0", |
| | | "vue-esign": "^1.1.4", |
| | | "vue-router": "4.4.0", |
| | | "vuedraggable": "4.1.0" |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | export function getMeetingRoomList(data) { |
| | | return request({ |
| | | url: "/meeting/roomList", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function saveRoom(data) { |
| | | return request({ |
| | | url: "/meeting/saveRoom", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function delRoom(id) { |
| | | return request({ |
| | | url: "/meeting/delRoom/"+id, |
| | | method: "delete", |
| | | }); |
| | | } |
| | | |
| | | export function getRoomEnum() { |
| | | return request({ |
| | | url: "/meeting/roomEnum", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | export function getDraftList(data){ |
| | | return request({ |
| | | url: "/meeting/draftList", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function saveDraft(data) { |
| | | return request({ |
| | | url: "/meeting/saveDraft", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function delDraft(id) { |
| | | return request({ |
| | | url: "/meeting/delDraft/"+id, |
| | | method: "delete", |
| | | }); |
| | | } |
| | | |
| | | export function saveMeetingApplication(data){ |
| | | return request({ |
| | | url: "/meeting/saveMeetingApplication", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function getExamineList(data) { |
| | | return request({ |
| | | url: "/meeting/applicationList", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | |
| | | export function getMeetingUseList(data){ |
| | | return request({ |
| | | url: "/meeting/meetingUseList", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function getMeetingPublish(data){ |
| | | return request({ |
| | | url: "/meeting/meetingPublishList", |
| | | method: "post", |
| | | data: data |
| | | }); |
| | | } |
| | | |
| | | |
| | | export function getMeetingMinutesByMeetingId(id){ |
| | | return request({ |
| | | url: "/meeting/getMeetingMinutesByMeetingId/"+id, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | export function saveMeetingMinutes(data){ |
| | | return request({ |
| | | url: "/meeting/saveMeetingMinutes", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | |
| | | export function getMeetSummary(){ |
| | | return request({ |
| | | url: "/meeting/getMeetSummary", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | export function getMeetSummaryItems(){ |
| | | return request({ |
| | | url: "/meeting/getMeetSummaryItems", |
| | | method: "get", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // å·¡æ£ç®¡ç |
| | | import request from '@/utils/request' |
| | | |
| | | // å·¡æ£ä»»å¡è¡¨è¡¨æ¥è¯¢ |
| | | export function inspectionTaskList(query) { |
| | | return request({ |
| | | url: '/inspectionTask/list', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | // å·¡æ£ä»»å¡è¡¨æ°å¢ä¿®æ¹ |
| | | export function addOrEditInspectionTask(query) { |
| | | return request({ |
| | | url: '/inspectionTask/addOrEditInspectionTask', |
| | | method: 'post', |
| | | data: query |
| | | }) |
| | | } |
| | | // å·¡æ£ä»»å¡è¡¨å é¤ |
| | | export function delInspectionTask(query) { |
| | | return request({ |
| | | url: '/inspectionTask/delInspectionTask', |
| | | method: 'delete', |
| | | data: query |
| | | }) |
| | | } |
| | | // 宿¶å·¡æ£ä»»å¡è¡¨å é¤ |
| | | export function delTimingTask(query) { |
| | | return request({ |
| | | url: '/timingTask/delTimingTask', |
| | | method: 'delete', |
| | | data: query |
| | | }) |
| | | } |
| | | |
| | | // /inspectionTask/addOrEditInspectionTask |
| | | // å·¡æ£ä¸ä¼ |
| | | export function uploadInspectionTask(query) { |
| | | return request({ |
| | | url: '/inspectionTask/addOrEditInspectionTask', |
| | | method: 'post', |
| | | data: query |
| | | }) |
| | | } |
| | | // 宿¶å·¡æ£ä»»å¡è¡¨æ¥è¯¢ |
| | | export function timingTaskList(query) { |
| | | return request({ |
| | | url: '/timingTask/list', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | // 宿¶å·¡æ£ä»»å¡è¡¨æ°å¢ä¿®æ¹ |
| | | export function addOrEditTimingTask(query) { |
| | | return request({ |
| | | url: '/timingTask/addOrEditTimingTask', |
| | | method: 'post', |
| | | data: query |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // å·¡æ£ä¸ä¼ |
| | | import request from '@/utils/request' |
| | | |
| | | // äºç»´ç 管ç表æ¥è¯¢ |
| | | export function qrCodeList(query) { |
| | | return request({ |
| | | url: '/qrCode/list', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | // äºç»´ç æ«ç è®°å½è¡¨æ¥è¯¢ |
| | | export function qrCodeScanRecordList(query) { |
| | | return request({ |
| | | url: '/qrCodeScanRecord/list', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | // äºç»´ç 管ç表æ°å¢ä¿®æ¹ |
| | | export function addOrEditQrCode(query) { |
| | | return request({ |
| | | url: '/qrCode/addOrEditQrCode', |
| | | method: 'post', |
| | | data: query |
| | | }) |
| | | } |
| | | // äºç»´ç æ«ç è®°å½è¡¨æ°å¢ä¿®æ¹ |
| | | export function addOrEditQrCodeRecord(query) { |
| | | return request({ |
| | | url: '/qrCodeScanRecord/addOrEditQrCodeRecord', |
| | | method: 'post', |
| | | data: query |
| | | }) |
| | | } |
| | | // äºç»´ç æ«ç è®°å½è¡¨æ°å¢ä¿®æ¹ |
| | | export function delQrCode(query) { |
| | | return request({ |
| | | url: '/qrCode/delQrCode', |
| | | method: 'delete', |
| | | data: query |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // ææ¡£ç®¡ç |
| | | import request from '@/utils/request' |
| | | |
| | | |
| | | // /system/user/listAll |
| | | // æ¥è¯¢ææç¨æ·å表 |
| | | export function userListAll() { |
| | | return request({ |
| | | url: '/system/user/listAll', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // /equipmentManagement/equipmentList |
| | | // æ¥è¯¢è®¾å¤å表 |
| | | export function getEquipmentList(query) { |
| | | return request({ |
| | | url: '/equipmentManagement/equipmentList', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // /coalInfo/coalInfoList |
| | | // æ¥è¯¢ç
¤ç§å表 |
| | | export function getCoalInfoList(query) { |
| | | return request({ |
| | | url: '/coalInfo/coalInfoList', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // /coalField/coalFieldList |
| | | // æ¥è¯¢ç
¤è´¨å段å表 |
| | | export function getCoalFieldList(query) { |
| | | return request({ |
| | | url: '/coalField/coalFieldList', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // é宿¥ä»·é¡µé¢æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // å页æ¥è¯¢æ¥ä»·åå表 |
| | | export function quotationList(query) { |
| | | return request({ |
| | | url: "/sales/quotation/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢æ¥ä»·å详æ
|
| | | export function getQuotationDetail(query) { |
| | | return request({ |
| | | url: "/sales/quotation/detail", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢æ¥ä»·å |
| | | export function addQuotation(data) { |
| | | return request({ |
| | | url: "/sales/quotation/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ä¿®æ¹æ¥ä»·å |
| | | export function updateQuotation(data) { |
| | | return request({ |
| | | url: "/sales/quotation/update", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å 餿¥ä»·å |
| | | export function deleteQuotation(query) { |
| | | return request({ |
| | | url: "/sales/quotation/delete", |
| | | method: "delete", |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | // å鿥价å |
| | | export function sendQuotation(data) { |
| | | return request({ |
| | | url: "/sales/quotation/send", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // æ¥ä»·å转订å |
| | | export function convertToOrder(data) { |
| | | return request({ |
| | | url: "/sales/quotation/convertToOrder", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢å®¢æ·å表 |
| | | export function getCustomerList(query) { |
| | | return request({ |
| | | url: "/basic/customer/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢äº§åå表 |
| | | export function getProductList(query) { |
| | | return request({ |
| | | url: "/basic/product/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢ä¸å¡åå表 |
| | | export function getSalespersonList(query) { |
| | | return request({ |
| | | url: "/system/user/salespersonList", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å¯¼åºæ¥ä»·å |
| | | export function exportQuotation(query) { |
| | | return request({ |
| | | url: "/sales/quotation/export", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | | |
| | | // æå°æ¥ä»·å |
| | | export function printQuotation(query) { |
| | | return request({ |
| | | url: "/sales/quotation/print", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | |
| | | import { createWebHistory, createRouter } from 'vue-router' |
| | | import { createWebHistory, createRouter } from "vue-router"; |
| | | /* Layout */ |
| | | import Layout from '@/layout' |
| | | import Layout from "@/layout"; |
| | | |
| | | /** |
| | | * Note: è·¯ç±é
置项 |
| | |
| | | * roles: ['admin', 'common'] // 访é®è·¯ç±çè§è²æé |
| | | * permissions: ['a:a:a', 'b:b:b'] // 访é®è·¯ç±çèåæé |
| | | * meta : { |
| | | noCache: true // å¦æè®¾ç½®ä¸ºtrueï¼åä¸ä¼è¢« <keep-alive> ç¼å(é»è®¤ false) |
| | | title: 'title' // 设置该路ç±å¨ä¾§è¾¹æ åé¢å
å±ä¸å±ç¤ºçåå |
| | | icon: 'svg-name' // 设置该路ç±ç徿 ï¼å¯¹åºè·¯å¾src/assets/icons/svg |
| | | breadcrumb: false // å¦æè®¾ç½®ä¸ºfalseï¼åä¸ä¼å¨breadcrumbé¢å
å±ä¸æ¾ç¤º |
| | | activeMenu: '/system/user' // å½è·¯ç±è®¾ç½®äºè¯¥å±æ§ï¼åä¼é«äº®ç¸å¯¹åºçä¾§è¾¹æ ã |
| | | } |
| | | noCache: true // å¦æè®¾ç½®ä¸ºtrueï¼åä¸ä¼è¢« <keep-alive> ç¼å(é»è®¤ false) |
| | | title: 'title' // 设置该路ç±å¨ä¾§è¾¹æ åé¢å
å±ä¸å±ç¤ºçåå |
| | | icon: 'svg-name' // 设置该路ç±ç徿 ï¼å¯¹åºè·¯å¾src/assets/icons/svg |
| | | breadcrumb: false // å¦æè®¾ç½®ä¸ºfalseï¼åä¸ä¼å¨breadcrumbé¢å
å±ä¸æ¾ç¤º |
| | | activeMenu: '/system/user' // å½è·¯ç±è®¾ç½®äºè¯¥å±æ§ï¼åä¼é«äº®ç¸å¯¹åºçä¾§è¾¹æ ã |
| | | } |
| | | */ |
| | | |
| | | // å
Œ
±è·¯ç± |
| | | export const constantRoutes = [ |
| | | { |
| | | path: '/redirect', |
| | | path: "/redirect", |
| | | component: Layout, |
| | | hidden: true, |
| | | children: [ |
| | | { |
| | | path: '/redirect/:path(.*)', |
| | | component: () => import('@/views/redirect/index.vue') |
| | | } |
| | | ] |
| | | path: "/redirect/:path(.*)", |
| | | component: () => import("@/views/redirect/index.vue"), |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: '/login', |
| | | component: () => import('@/views/login'), |
| | | hidden: true |
| | | }, |
| | | { |
| | | path: "/callbacklccpn", |
| | | component: () => import("@/views/tideLogin.vue"), |
| | | path: "/login", |
| | | component: () => import("@/views/login"), |
| | | hidden: true, |
| | | }, |
| | | { |
| | | path: '/register', |
| | | component: () => import('@/views/register'), |
| | | hidden: true |
| | | path: "/register", |
| | | component: () => import("@/views/register"), |
| | | hidden: true, |
| | | }, |
| | | { |
| | | path: "/:pathMatch(.*)*", |
| | | component: () => import('@/views/error/404'), |
| | | hidden: true |
| | | component: () => import("@/views/error/404"), |
| | | hidden: true, |
| | | }, |
| | | { |
| | | path: '/401', |
| | | component: () => import('@/views/error/401'), |
| | | hidden: true |
| | | path: "/401", |
| | | component: () => import("@/views/error/401"), |
| | | hidden: true, |
| | | }, |
| | | |
| | | { |
| | | path: '', |
| | | path: "", |
| | | component: Layout, |
| | | redirect: '/index', |
| | | redirect: "/index", |
| | | children: [ |
| | | { |
| | | path: '/index', |
| | | component: () => import('@/views/index'), |
| | | name: 'Index', |
| | | meta: { title: 'é¦é¡µ', icon: 'dashboard', affix: true } |
| | | } |
| | | ] |
| | | path: "/index", |
| | | component: () => import("@/views/index"), |
| | | name: "Index", |
| | | meta: { title: "é¦é¡µ", icon: "dashboard", affix: true }, |
| | | }, |
| | | ], |
| | | }, |
| | | // { |
| | | // 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', |
| | | 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, |
| | | redirect: 'noredirect', |
| | | redirect: "noredirect", |
| | | children: [ |
| | | { |
| | | path: "profile", |
| | |
| | | name: "DeviceInfo", |
| | | meta: { title: "设å¤ä¿¡æ¯", icon: "monitor" }, |
| | | }, |
| | | { |
| | | path: "/data-dashboard", |
| | | component: () => import("@/views/reportAnalysis/dataDashboard/index.vue"), |
| | | hidden: true, |
| | | name: "DataDashboard", |
| | | meta: { title: "æ°æ®å¤§å±", icon: "dashboard" }, |
| | | }, |
| | | ]; |
| | | |
| | | // å¨æè·¯ç±ï¼åºäºç¨æ·æé卿å»å è½½ |
| | | export const dynamicRoutes = [ |
| | | { |
| | | path: '/system/user-auth', |
| | | path: "/system/user-auth", |
| | | component: Layout, |
| | | hidden: true, |
| | | permissions: ['system:user:edit'], |
| | | permissions: ["system:user:edit"], |
| | | children: [ |
| | | { |
| | | path: 'role/:userId(\\d+)', |
| | | component: () => import('@/views/system/user/authRole'), |
| | | name: 'AuthRole', |
| | | meta: { title: 'åé
è§è²', activeMenu: '/system/user' } |
| | | } |
| | | ] |
| | | path: "role/:userId(\\d+)", |
| | | component: () => import("@/views/system/user/authRole"), |
| | | name: "AuthRole", |
| | | meta: { title: "åé
è§è²", activeMenu: "/system/user" }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: '/system/role-auth', |
| | | path: "/system/role-auth", |
| | | component: Layout, |
| | | hidden: true, |
| | | permissions: ['system:role:edit'], |
| | | permissions: ["system:role:edit"], |
| | | children: [ |
| | | { |
| | | path: 'user/:roleId(\\d+)', |
| | | component: () => import('@/views/system/role/authUser'), |
| | | name: 'AuthUser', |
| | | meta: { title: 'åé
ç¨æ·', activeMenu: '/system/role' } |
| | | } |
| | | ] |
| | | path: "user/:roleId(\\d+)", |
| | | component: () => import("@/views/system/role/authUser"), |
| | | name: "AuthUser", |
| | | meta: { title: "åé
ç¨æ·", activeMenu: "/system/role" }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: '/system/dict-data', |
| | | path: "/system/dict-data", |
| | | component: Layout, |
| | | hidden: true, |
| | | permissions: ['system:dict:list'], |
| | | permissions: ["system:dict:list"], |
| | | children: [ |
| | | { |
| | | path: 'index/:dictId(\\d+)', |
| | | component: () => import('@/views/system/dict/data'), |
| | | name: 'Data', |
| | | meta: { title: 'åå
¸æ°æ®', activeMenu: '/system/dict' } |
| | | } |
| | | ] |
| | | path: "index/:dictId(\\d+)", |
| | | component: () => import("@/views/system/dict/data"), |
| | | name: "Data", |
| | | meta: { title: "åå
¸æ°æ®", activeMenu: "/system/dict" }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: '/monitor/job-log', |
| | | path: "/monitor/job-log", |
| | | component: Layout, |
| | | hidden: true, |
| | | permissions: ['monitor:job:list'], |
| | | permissions: ["monitor:job:list"], |
| | | children: [ |
| | | { |
| | | path: 'index/:jobId(\\d+)', |
| | | component: () => import('@/views/monitor/job/log'), |
| | | name: 'JobLog', |
| | | meta: { title: 'è°åº¦æ¥å¿', activeMenu: '/monitor/job' } |
| | | } |
| | | ] |
| | | path: "index/:jobId(\\d+)", |
| | | component: () => import("@/views/monitor/job/log"), |
| | | name: "JobLog", |
| | | meta: { title: "è°åº¦æ¥å¿", activeMenu: "/monitor/job" }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: '/tool/gen-edit', |
| | | path: "/tool/gen-edit", |
| | | component: Layout, |
| | | hidden: true, |
| | | permissions: ['tool:gen:edit'], |
| | | permissions: ["tool:gen:edit"], |
| | | children: [ |
| | | { |
| | | path: 'index/:tableId(\\d+)', |
| | | component: () => import('@/views/tool/gen/editTable'), |
| | | name: 'GenEdit', |
| | | meta: { title: 'ä¿®æ¹çæé
ç½®', activeMenu: '/tool/gen' } |
| | | } |
| | | ] |
| | | } |
| | | ] |
| | | path: "index/:tableId(\\d+)", |
| | | component: () => import("@/views/tool/gen/editTable"), |
| | | name: "GenEdit", |
| | | meta: { title: "ä¿®æ¹çæé
ç½®", activeMenu: "/tool/gen" }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]; |
| | | |
| | | const router = createRouter({ |
| | | history: createWebHistory(), |
| | | routes: constantRoutes, |
| | | scrollBehavior(to, from, savedPosition) { |
| | | if (savedPosition) { |
| | | return savedPosition |
| | | return savedPosition; |
| | | } |
| | | return { top: 0 } |
| | | return { top: 0 }; |
| | | }, |
| | | }) |
| | | }); |
| | | |
| | | export default router |
| | | export default router; |
| | |
| | | </el-card> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-number">{{ stats.ongoing }}</div> |
| | | <div class="stat-number">{{ stats.underWay }}</div> |
| | | <div class="stat-label">è¿è¡ä¸</div> |
| | | </div> |
| | | </el-card> |
| | |
| | | </el-card> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-number">{{ stats.upcoming }}</div> |
| | | <div class="stat-number">{{ stats.toStart }}</div> |
| | | <div class="stat-label">å³å°å¼å§</div> |
| | | </div> |
| | | </el-card> |
| | |
| | | </el-tag> |
| | | </div> |
| | | <div class="meeting-time"> |
| | | <el-icon><Clock /></el-icon> |
| | | {{ formatTime(meeting.startTime) }} - {{ formatTime(meeting.endTime) }} |
| | | {{dayjs(meeting.startTime).format("YYYY-MM-DD")}}<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> |
| | |
| | | </div> |
| | | |
| | | <div class="meeting-agenda"> |
| | | <h4>è®®ç¨å®æ</h4> |
| | | <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 class="editor-container"> |
| | | <div |
| | | v-html="meeting.content" |
| | | /> |
| | | </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> |
| | | |
| | |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { Clock, Location, User, UserFilled } from '@element-plus/icons-vue' |
| | | import Editor from "@/components/Editor/index.vue"; |
| | | import {getMeetSummaryItems,getMeetSummary} from '@/api/collaborativeApproval/meeting.js' |
| | | import dayjs from "dayjs"; |
| | | |
| | | // ç»è®¡æ°æ® |
| | | const stats = reactive({ |
| | | total: 12, |
| | | ongoing: 3, |
| | | completed: 7, |
| | | upcoming: 2 |
| | | const stats = ref({ |
| | | total: 0, |
| | | underWay: 0, |
| | | completed: 0, |
| | | toStart: 0 |
| | | }) |
| | | |
| | | // ä¼è®®æ°æ® |
| | | const meetings = ref([ |
| | | { |
| | | id: 1, |
| | | title: '产åå¼åå¨ä¼', |
| | | status: 'ongoing', |
| | | startTime: '2025-01-15 09:00:00', |
| | | endTime: '2025-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: '2025-01-15 14:00:00', |
| | | endTime: '2025-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: '2025-01-14 16:00:00', |
| | | endTime: '2025-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 getStatusType = (status) => { |
| | | const statusMap = { |
| | | 'ongoing': 'success', |
| | | 'upcoming': 'warning', |
| | | 'completed': 'info' |
| | | '2': 'success', |
| | | '1': 'warning', |
| | | '0': 'info' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | |
| | | // è·åç¶æææ¬ |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | 'ongoing': 'è¿è¡ä¸', |
| | | 'upcoming': 'å³å°å¼å§', |
| | | 'completed': '已宿' |
| | | '2': 'è¿è¡ä¸', |
| | | '1': 'å³å°å¼å§', |
| | | '0': '已宿' |
| | | } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | |
| | | return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) |
| | | } |
| | | |
| | | // å建ä¼è®® |
| | | const createMeeting = () => { |
| | | dialogVisible.value = true |
| | | // é置表å |
| | | Object.assign(meetingForm, { |
| | | title: '', |
| | | timeRange: [], |
| | | location: '', |
| | | host: '', |
| | | description: '' |
| | | |
| | | onMounted( async () => { |
| | | let [resp1,resp2] = await Promise.all([getMeetSummary(),getMeetSummaryItems()]) |
| | | stats.value = resp1.data |
| | | meetings.value = resp2.data.map(item => { |
| | | return { |
| | | ...item, |
| | | participants: JSON.parse(item.participants) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // æäº¤ä¼è®® |
| | | 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> |
| | |
| | | .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> |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>ä¼è®®ç³è¯·</h2> |
| | | </div> |
| | | |
| | | <!-- ç³è¯·ç±»åéæ© --> |
| | | <el-card class="type-card"> |
| | | <div class="type-selector"> |
| | | <div |
| | | v-for="type in applicationTypes" |
| | | :key="type.value" |
| | | class="type-item" |
| | | :class="{ active: currentType === type.value }" |
| | | @click="changeType(type.value)" |
| | | > |
| | | <div class="type-icon"> |
| | | <el-icon :size="24"><component :is="type.icon"/></el-icon> |
| | | </div> |
| | | <div class="type-info"> |
| | | <div class="type-name">{{ type.name }}</div> |
| | | <div class="type-desc">{{ type.desc }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®ç³è¯·è¡¨å --> |
| | | <el-card> |
| | | <div class="form-header"> |
| | | <h3>{{ getCurrentTypeName() }}ç³è¯·</h3> |
| | | </div> |
| | | |
| | | <el-form |
| | | ref="meetingFormRef" |
| | | :model="meetingForm" |
| | | :rules="rules" |
| | | label-width="100px" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼è®®ä¸»é¢" prop="title"> |
| | | <el-input v-model="meetingForm.title" placeholder="请è¾å
¥ä¼è®®ä¸»é¢"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼è®®å®¤" prop="roomId"> |
| | | <el-select v-model="meetingForm.roomId" placeholder="è¯·éæ©ä¼è®®å®¤" style="width: 100%"> |
| | | <el-option |
| | | v-for="room in meetingRooms" |
| | | :key="room.id" |
| | | :label="`${room.name} (${room.location})`" |
| | | :value="room.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="主æäºº" prop="host"> |
| | | <el-input v-model="meetingForm.host" placeholder="请è¾å
¥ä¸»æäººå§å"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼è®®æ¥æ" prop="meetingDate"> |
| | | <el-date-picker |
| | | v-model="meetingForm.meetingDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©ä¼è®®æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | :disabled-date="disabledDate" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <!-- 空åï¼ä¿æå¸å± --> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¼å§æ¶é´" prop="startTime"> |
| | | <el-select |
| | | v-model="meetingForm.startTime" |
| | | placeholder="è¯·éæ©å¼å§æ¶é´" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="time in timeOptions" |
| | | :key="time.value" |
| | | :label="time.label" |
| | | :value="time.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»ææ¶é´" prop="endTime"> |
| | | <el-select |
| | | v-model="meetingForm.endTime" |
| | | placeholder="è¯·éæ©ç»ææ¶é´" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="time in timeOptions" |
| | | :key="time.value" |
| | | :label="time.label" |
| | | :value="time.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-form-item label="åä¼äººå" prop="participants"> |
| | | <el-select |
| | | v-model="meetingForm.participants" |
| | | multiple |
| | | filterable |
| | | placeholder="è¯·éæ©åä¼äººå" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="person in employees" |
| | | :key="person.id" |
| | | :label="`${person.staffName} (${person.postJob})`" |
| | | :value="person.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ä¼è®®è¯´æ" prop="description"> |
| | | <el-input |
| | | v-model="meetingForm.description" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请è¾å
¥ä¼è®®è¯´æ" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="form-footer"> |
| | | <el-button @click="resetForm">éç½®</el-button> |
| | | <el-button type="primary" @click="submitForm">æäº¤</el-button> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref, reactive, onMounted} from 'vue' |
| | | import {ElMessage} from 'element-plus' |
| | | import {Plus, Document, Promotion, Bell} from '@element-plus/icons-vue' |
| | | import {getRoomEnum, saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js' |
| | | import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js"; |
| | | |
| | | // å½åç³è¯·ç±»å |
| | | const currentType = ref('department') // approval: å®¡æ¹æµç¨, department: é¨é¨çº§, notification: éç¥åå¸ |
| | | |
| | | // ç³è¯·ç±»åé项 |
| | | const applicationTypes = ref([ |
| | | { |
| | | value: 'approval', |
| | | name: 'å®¡æ¹æµç¨ä¼è®®', |
| | | desc: 'éè¦ç»è¿å¤çº§å®¡æ¹çä¼è®®ç³è¯·', |
| | | icon: Document |
| | | }, |
| | | { |
| | | value: 'department', |
| | | name: 'é¨é¨çº§ä¼è®®', |
| | | desc: 'é¨é¨å
é¨ä¼è®®ç³è¯·æµç¨', |
| | | icon: Promotion |
| | | }, |
| | | { |
| | | value: 'notification', |
| | | name: 'ä¼è®®éç¥', |
| | | desc: 'æ é审æ¹ç´æ¥åå¸çä¼è®®éç¥', |
| | | icon: Bell |
| | | } |
| | | ]) |
| | | |
| | | // è¡¨åæ°æ® |
| | | const meetingForm = reactive({ |
| | | title: '', |
| | | type: '', |
| | | roomId: '', |
| | | host: '', |
| | | meetingDate: '', |
| | | startTime: '', |
| | | endTime: '', |
| | | participants: [], |
| | | description: '' |
| | | }) |
| | | |
| | | // è¡¨åæ ¡éªè§å |
| | | const rules = { |
| | | title: [{required: true, message: '请è¾å
¥ä¼è®®ä¸»é¢', trigger: 'blur'}], |
| | | roomId: [{required: true, message: 'è¯·éæ©ä¼è®®å®¤', trigger: 'change'}], |
| | | host: [{required: true, message: '请è¾å
¥ä¸»æäºº', trigger: 'blur'}], |
| | | meetingDate: [{required: true, message: 'è¯·éæ©ä¼è®®æ¥æ', trigger: 'change'}], |
| | | startTime: [{required: true, message: 'è¯·éæ©å¼å§æ¶é´', trigger: 'change'}], |
| | | endTime: [{required: true, message: 'è¯·éæ©ç»ææ¶é´', trigger: 'change'}], |
| | | participants: [{required: true, message: 'è¯·éæ©åä¼äººå', trigger: 'change'}] |
| | | } |
| | | |
| | | // 表åå¼ç¨ |
| | | const meetingFormRef = ref(null) |
| | | |
| | | // ä¼è®®å®¤å表 |
| | | const meetingRooms = ref([]) |
| | | |
| | | // åå·¥å表 |
| | | const employees = ref([]) |
| | | |
| | | // æ¶é´é项ï¼ä»¥åå°æ¶ä¸ºé´éï¼ |
| | | const timeOptions = ref([]) |
| | | |
| | | // åå§åæ¶é´é项 |
| | | const initTimeOptions = () => { |
| | | const options = [] |
| | | for (let hour = 8; hour <= 18; hour++) { |
| | | // æ¯ä¸ªå°æ¶æ·»å 两个éé¡¹ï¼æ´ç¹ååç¹ |
| | | options.push({ |
| | | value: `${hour.toString().padStart(2, '0')}:00`, |
| | | label: `${hour.toString().padStart(2, '0')}:00` |
| | | }) |
| | | |
| | | if (hour < 18) { // 18:00ä¹å没æåç¹é项 |
| | | options.push({ |
| | | value: `${hour.toString().padStart(2, '0')}:30`, |
| | | label: `${hour.toString().padStart(2, '0')}:30` |
| | | }) |
| | | } |
| | | } |
| | | timeOptions.value = options |
| | | } |
| | | |
| | | // ç¦ç¨æ¥æï¼ç¦ç¨ä»å¤©ä¹åçæ¥æï¼ |
| | | const disabledDate = (time) => { |
| | | // ç¦ç¨ä»å¤©ä¹åçæ¥æ |
| | | return time.getTime() < Date.now() - 86400000 |
| | | } |
| | | |
| | | // 忢ç³è¯·ç±»å |
| | | const changeType = (type) => { |
| | | currentType.value = type |
| | | } |
| | | |
| | | // è·åå½åç±»ååç§° |
| | | const getCurrentTypeName = () => { |
| | | const type = applicationTypes.value.find(t => t.value === currentType.value) |
| | | return type ? type.name : '' |
| | | } |
| | | |
| | | // é置表å |
| | | const resetForm = () => { |
| | | meetingFormRef.value?.resetFields() |
| | | } |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | meetingFormRef.value?.validate((valid) => { |
| | | if (valid) { |
| | | |
| | | let formData = {...meetingForm} |
| | | formData.applicationType = currentType.value |
| | | formData.startTime = `${meetingForm.meetingDate} ${meetingForm.startTime}:00` |
| | | formData.endTime = `${meetingForm.meetingDate} ${meetingForm.endTime}:00` |
| | | formData.participants = JSON.stringify(formData.participants) |
| | | console.log(formData) |
| | | saveMeetingApplication(formData).then(() => { |
| | | |
| | | // 模ææäº¤æä½ |
| | | ElMessage.success(`${getCurrentTypeName()}æäº¤æå`) |
| | | |
| | | // æ ¹æ®ä¸åç±»åæ§è¡ä¸åæä½ |
| | | switch (currentType.value) { |
| | | case 'approval': |
| | | ElMessage.info('ä¼è®®å·²æäº¤å®¡æ¹æµç¨') |
| | | break |
| | | case 'department': |
| | | ElMessage.info('é¨é¨çº§ä¼è®®ç³è¯·å·²æäº¤') |
| | | break |
| | | case 'notification': |
| | | ElMessage.info('ä¼è®®éç¥å·²åå¸') |
| | | break |
| | | } |
| | | resetForm() |
| | | }) |
| | | |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶åå§å |
| | | onMounted(() => { |
| | | initTimeOptions() |
| | | getRoomEnum().then(res => { |
| | | meetingRooms.value = res.data |
| | | }) |
| | | getStaffOnJob().then(res => { |
| | | employees.value = res.data.sort((a, b) => a.postJob.localeCompare(b.postJob)) |
| | | }) |
| | | }) |
| | | </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; |
| | | } |
| | | |
| | | .type-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .type-selector { |
| | | display: flex; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .type-item { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 20px; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 8px; |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .type-item:hover { |
| | | border-color: #409eff; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .type-item.active { |
| | | border-color: #409eff; |
| | | background-color: #ecf5ff; |
| | | } |
| | | |
| | | .type-icon { |
| | | margin-right: 15px; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .type-name { |
| | | font-size: 16px; |
| | | font-weight: 500; |
| | | color: #303133; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .type-desc { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .form-header { |
| | | margin-bottom: 20px; |
| | | padding-bottom: 15px; |
| | | border-bottom: 1px solid #ebeef5; |
| | | } |
| | | |
| | | .form-header h3 { |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .form-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | margin-top: 30px; |
| | | padding-top: 20px; |
| | | border-top: 1px solid #ebeef5; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>ä¼è®®è稿</h2> |
| | | <el-button type="primary" @click="handleAdd"> |
| | | <el-icon><Plus /></el-icon> |
| | | æ°å»ºè稿 |
| | | </el-button> |
| | | </div> |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <el-card class="search-card"> |
| | | <el-form :model="searchForm" label-width="100px" inline> |
| | | <el-form-item label="ä¼è®®ä¸»é¢"> |
| | | <el-input v-model="searchForm.title" placeholder="请è¾å
¥ä¼è®®ä¸»é¢" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¼è®®æ¥æ"> |
| | | <el-date-picker |
| | | v-model="searchForm.meetingDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©ä¼è®®æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- è稿å表 --> |
| | | <el-card> |
| | | <el-table v-loading="loading" :data="draftList" border> |
| | | <el-table-column prop="title" label="ä¼è®®ä¸»é¢" align="center" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column prop="room" label="ä¼è®®å®¤" align="center" width="120" /> |
| | | <el-table-column prop="host" label="主æäºº" align="center" width="120" /> |
| | | <el-table-column prop="meetingTime" label="ä¼è®®æ¶é´" align="center" width="180"> |
| | | <template #default="scope"> |
| | | {{ formatDateTime(scope.row.meetingTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="participants" label="åä¼äººæ°" align="center" width="100"> |
| | | <template #default="scope"> |
| | | {{ scope.row.participants }}人 |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="createTime" label="å建æ¶é´" align="center" width="180" /> |
| | | <el-table-column label="æä½" align="center" width="200" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" link @click="viewDraft(scope.row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="editDraft(scope.row)">ç¼è¾</el-button> |
| | | <el-button type="danger" link @click="deleteDraft(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | v-model:page="queryParams.current" |
| | | v-model:limit="queryParams.size" |
| | | @pagination="getList" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®è稿详æ
å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | title="ä¼è®®è稿详æ
" |
| | | v-model="detailDialogVisible" |
| | | width="800px" |
| | | > |
| | | <div v-if="currentDraft"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="ä¼è®®ä¸»é¢">{{ currentDraft.title }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®ç¼å·">{{ currentDraft.meetingId }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®å®¤">{{ currentDraft.room }}</el-descriptions-item> |
| | | <el-descriptions-item label="主æäºº">{{ currentDraft.host }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®æ¶é´" :span="2"> |
| | | {{ formatDateTime(currentDraft.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å建æ¶é´">{{ currentDraft.createTime }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="content-section mt-20"> |
| | | <h4>åä¼äººå</h4> |
| | | <div class="participants-list"> |
| | | {{ currentDraft.participantList }} |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="content-section mt-20"> |
| | | <h4>ä¼è®®è¯´æ</h4> |
| | | <div class="meeting-description">{{ currentDraft.description }}</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="detailDialogVisible = false">å
³ é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ°å»º/ç¼è¾èç¨¿å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | :title="dialogTitle" |
| | | v-model="editDialogVisible" |
| | | width="700px" |
| | | > |
| | | <el-form :model="meetingForm" :rules="rules" ref="meetingFormRef" label-width="100px"> |
| | | <el-form-item label="ä¼è®®ä¸»é¢" prop="title"> |
| | | <el-input v-model="meetingForm.title" placeholder="请è¾å
¥ä¼è®®ä¸»é¢" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¼è®®å®¤" prop="room"> |
| | | <el-select v-model="meetingForm.roomId" placeholder="è¯·éæ©ä¼è®®å®¤" style="width: 100%"> |
| | | <el-option v-for="(v,k) in roomList" :label="v.name" :value="v.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="主æäºº" prop="host"> |
| | | <el-input v-model="meetingForm.host" placeholder="请è¾å
¥ä¸»æäºº" /> |
| | | </el-form-item> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼è®®æ¥æ" prop="meetingDate"> |
| | | <el-date-picker |
| | | v-model="meetingForm.meetingDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©ä¼è®®æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | :disabled-date="disabledDate" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <!-- 空åï¼ä¿æå¸å± --> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¼å§æ¶é´" prop="startTime"> |
| | | <el-select |
| | | v-model="meetingForm.startTime" |
| | | placeholder="è¯·éæ©å¼å§æ¶é´" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="time in timeOptions" |
| | | :key="time.value" |
| | | :label="time.label" |
| | | :value="time.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»ææ¶é´" prop="endTime"> |
| | | <el-select |
| | | v-model="meetingForm.endTime" |
| | | placeholder="è¯·éæ©ç»ææ¶é´" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="time in timeOptions" |
| | | :key="time.value" |
| | | :label="time.label" |
| | | :value="time.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="åä¼äººæ°" prop="participants"> |
| | | <el-input |
| | | v-model="meetingForm.participants" |
| | | type="number" |
| | | placeholder="请è¾å
¥åä¼äººæ°" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="åä¼äººå" prop="participants"> |
| | | <el-input |
| | | v-model="meetingForm.participantList" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥åä¼äººåï¼ç¨éå·åé" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¼è®®è¯´æ"> |
| | | <el-input |
| | | v-model="meetingForm.description" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请è¾å
¥ä¼è®®è¯´æ" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="editDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ä¿ å</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/Pagination/index.vue' |
| | | import {getRoomEnum,getDraftList,saveDraft,delDraft} from '@/api/collaborativeApproval/meeting.js' |
| | | import dayjs from "dayjs"; |
| | | // æ°æ®å表å è½½ç¶æ |
| | | const loading = ref(false) |
| | | |
| | | // æ»æ¡æ° |
| | | const total = ref(0) |
| | | |
| | | // è稿åè¡¨æ°æ® |
| | | const draftList = ref([]) |
| | | |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10 |
| | | }) |
| | | |
| | | // æç´¢è¡¨å |
| | | const searchForm = reactive({ |
| | | title: '', |
| | | meetingDate: '' |
| | | }) |
| | | |
| | | // æ¯å¦æ¾ç¤ºå¯¹è¯æ¡ |
| | | const detailDialogVisible = ref(false) |
| | | const editDialogVisible = ref(false) |
| | | |
| | | const roomList = ref([]) |
| | | |
| | | // å¯¹è¯æ¡æ é¢ |
| | | const dialogTitle = ref('') |
| | | |
| | | // å½åæ¥ççè稿 |
| | | const currentDraft = ref(null) |
| | | |
| | | // 表åå¼ç¨ |
| | | const meetingFormRef = ref(null) |
| | | |
| | | // æ¶é´é项ï¼ä»¥åå°æ¶ä¸ºé´éï¼å·¥ä½æ¶é´8:00-18:00ï¼ |
| | | const timeOptions = ref([]) |
| | | |
| | | // è¡¨åæ°æ® |
| | | const meetingForm = reactive({ |
| | | id: '', |
| | | meetingId: '', |
| | | title: '', |
| | | roomId: '', |
| | | host: '', |
| | | meetingDate: '', |
| | | startTime: '', |
| | | endTime: '', |
| | | participants: 0, |
| | | participantList: '', |
| | | description: '', |
| | | createTime: '' |
| | | }) |
| | | |
| | | // è¡¨åæ ¡éªè§å |
| | | const rules = { |
| | | title: [{ required: true, message: '请è¾å
¥ä¼è®®ä¸»é¢', trigger: 'blur' }], |
| | | roomId: [{ required: true, message: 'è¯·éæ©ä¼è®®å®¤', trigger: 'change' }], |
| | | host: [{ required: true, message: '请è¾å
¥ä¸»æäºº', trigger: 'blur' }], |
| | | meetingDate: [{ required: true, message: 'è¯·éæ©ä¼è®®æ¥æ', trigger: 'change' }], |
| | | startTime: [{ required: true, message: 'è¯·éæ©å¼å§æ¶é´', trigger: 'change' }], |
| | | endTime: [{ required: true, message: 'è¯·éæ©ç»ææ¶é´', trigger: 'change' }] |
| | | } |
| | | |
| | | // åå§åæ¶é´é项ï¼ä»¥åå°æ¶ä¸ºé´éï¼å·¥ä½æ¶é´8:00-18:00ï¼ |
| | | const initTimeOptions = () => { |
| | | const options = [] |
| | | for (let hour = 8; hour <= 18; hour++) { |
| | | // æ¯ä¸ªå°æ¶æ·»å 两个éé¡¹ï¼æ´ç¹ååç¹ |
| | | options.push({ |
| | | value: `${hour.toString().padStart(2, '0')}:00`, |
| | | label: `${hour.toString().padStart(2, '0')}:00` |
| | | }) |
| | | |
| | | if (hour < 18) { // 18:00ä¹å没æåç¹é项 |
| | | options.push({ |
| | | value: `${hour.toString().padStart(2, '0')}:30`, |
| | | label: `${hour.toString().padStart(2, '0')}:30` |
| | | }) |
| | | } |
| | | } |
| | | timeOptions.value = options |
| | | } |
| | | |
| | | // ç¦ç¨æ¥æï¼ç¦ç¨ä»å¤©ä¹åçæ¥æï¼ |
| | | const disabledDate = (time) => { |
| | | // ç¦ç¨ä»å¤©ä¹åçæ¥æ |
| | | return time.getTime() < Date.now() - 86400000 |
| | | } |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const getList = async () => { |
| | | loading.value = true |
| | | |
| | | let resp = await getDraftList({...queryParams,...searchForm}) |
| | | queryParams.current = resp.data.current |
| | | draftList.value = resp.data.records.map(it=>{ |
| | | it.room = roomList.value.find(room=>it.roomId===room.id).name ?? "" |
| | | it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format("HH:mm")} ~ ${dayjs(it.endTime).format("HH:mm")}` |
| | | return it |
| | | }) |
| | | |
| | | loading.value = false |
| | | |
| | | } |
| | | |
| | | // æç´¢æé®æä½ |
| | | const handleSearch = () => { |
| | | queryParams.pageNum = 1 |
| | | getList() |
| | | } |
| | | |
| | | // éç½®æç´¢è¡¨å |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | title: '', |
| | | createTime: [] |
| | | }) |
| | | handleSearch() |
| | | } |
| | | |
| | | // æ·»å æé®æä½ |
| | | const handleAdd = () => { |
| | | dialogTitle.value = 'æ°å»ºè稿' |
| | | resetForm() |
| | | editDialogVisible.value = true |
| | | } |
| | | |
| | | // æ¥çè稿详æ
|
| | | const viewDraft = (row) => { |
| | | currentDraft.value = row |
| | | detailDialogVisible.value = true |
| | | } |
| | | |
| | | // ç¼è¾è稿 |
| | | const editDraft = (row) => { |
| | | dialogTitle.value = 'ç¼è¾è稿' |
| | | Object.assign(meetingForm, { |
| | | id: row.id, |
| | | meetingId: row.meetingId, |
| | | title: row.title, |
| | | room: row.room, |
| | | roomId: row.id, |
| | | host: row.host, |
| | | meetingDate: row.meetingTime.split(' ')[0], |
| | | startTime: row.meetingTime.split(' ')[1], |
| | | endTime: row.meetingTime.split(' ')[3], |
| | | participants: row.participants, |
| | | participantList: row.participantList, |
| | | description: row.description, |
| | | createTime: row.createTime |
| | | }) |
| | | editDialogVisible.value = true |
| | | } |
| | | |
| | | // å é¤è稿 |
| | | const deleteDraft = (row) => { |
| | | ElMessageBox.confirm( |
| | | `确认å é¤ä¼è®®è稿 "${row.title}"?`, |
| | | 'å é¤è稿', |
| | | { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | } |
| | | ).then(() => { |
| | | delDraft(row.id).then(resp=>{ |
| | | ElMessage.success('è稿å 餿å') |
| | | getList() |
| | | }) |
| | | |
| | | }).catch(() => {}) |
| | | } |
| | | |
| | | // é置表å |
| | | const resetForm = () => { |
| | | Object.assign(meetingForm, { |
| | | id: '', |
| | | meetingId: '', |
| | | title: '', |
| | | room: '', |
| | | host: '', |
| | | meetingDate: '', |
| | | startTime: '', |
| | | endTime: '', |
| | | participants: 0, |
| | | participantList: '', |
| | | description: '', |
| | | createTime: '' |
| | | }) |
| | | } |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | meetingFormRef.value.validate((valid) => { |
| | | if (valid) { |
| | | let formData = {...meetingForm} |
| | | formData.startTime = dayjs(meetingForm.meetingDate + ' ' + meetingForm.startTime).format("YYYY-MM-DD HH:mm:ss") |
| | | formData.endTime = dayjs(meetingForm.meetingDate + ' ' + meetingForm.endTime).format("YYYY-MM-DD HH:mm:ss") |
| | | saveDraft(formData).then(()=>{ |
| | | ElMessage.success('ä¿åæå') |
| | | editDialogVisible.value = false |
| | | getList() |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | | const formatDateTime = (dateTime) => { |
| | | if (!dateTime) return '' |
| | | return dateTime.replace(' ', '\n') |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶è·åæ°æ® |
| | | onMounted(() => { |
| | | initTimeOptions() |
| | | getList() |
| | | getRoomEnum().then((res) => { |
| | | roomList.value = res.data |
| | | }) |
| | | }) |
| | | </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; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .content-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .participants-list { |
| | | min-height: 40px; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .meeting-description { |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | white-space: pre-wrap; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>ä¼è®®å®¡æ¹</h2> |
| | | </div> |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <el-card class="search-card"> |
| | | <el-form :model="searchForm" inline> |
| | | <el-form-item label="ä¼è®®ä¸»é¢"> |
| | | <el-input v-model="searchForm.title" placeholder="请è¾å
¥ä¼è®®ä¸»é¢" clearable/> |
| | | </el-form-item> |
| | | <el-form-item label="ç³è¯·äºº"> |
| | | <el-input v-model="searchForm.applicant" placeholder="请è¾å
¥ç³è¯·äºº" clearable/> |
| | | </el-form-item> |
| | | <el-form-item label="审æ¹ç¶æ"> |
| | | <el-select style="width: 100px" v-model="searchForm.status" placeholder="è¯·éæ©å®¡æ¹ç¶æ" clearable> |
| | | <el-option label="å¾
审æ¹" value="0"/> |
| | | <el-option label="å·²éè¿" value="1"/> |
| | | <el-option label="æªå®¡æ¹" value="2"/> |
| | | <el-option label="已忶" value="3"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®å®¡æ¹å表 --> |
| | | <el-card> |
| | | <el-table v-loading="loading" :data="approvalList" border> |
| | | <el-table-column prop="title" label="ä¼è®®ä¸»é¢" align="center" min-width="200" show-overflow-tooltip/> |
| | | <el-table-column prop="applicant" label="ç³è¯·äºº" align="center" width="120"/> |
| | | <el-table-column prop="host" label="主ç人" align="center" width="120"/> |
| | | <el-table-column prop="meetingTime" label="ä¼è®®æ¶é´" align="center" width="180"> |
| | | <template #default="scope"> |
| | | {{ formatDateTime(scope.row.meetingTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="location" label="ä¼è®®å°ç¹" align="center" width="150"/> |
| | | <el-table-column prop="participants" label="åä¼äººæ°" align="center" width="100"> |
| | | <template #default="scope"> |
| | | {{ scope.row.participants.length }}人 |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" label="审æ¹ç¶æ" align="center" width="120"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ getStatusText(scope.row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" align="center" width="200" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" link @click="viewDetail(scope.row)">æ¥ç</el-button> |
| | | <el-button |
| | | v-if="scope.row.status == '0'" |
| | | type="primary" |
| | | link |
| | | @click="handleApproval(scope.row)" |
| | | > |
| | | å®¡æ¹ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | v-model:page="queryParams.current" |
| | | v-model:limit="queryParams.size" |
| | | @pagination="getList" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®è¯¦æ
å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | title="ä¼è®®è¯¦æ
" |
| | | v-model="detailDialogVisible" |
| | | width="800px" |
| | | > |
| | | <div v-if="currentMeeting"> |
| | | <el-descriptions label-width="100px" class="meeting-desc" :column="2" border> |
| | | <el-descriptions-item label="ä¼è®®ä¸»é¢" label-class-name="nowrap-label">{{ |
| | | currentMeeting.title |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·äºº" label-class-name="nowrap-label">{{ |
| | | currentMeeting.applicant |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="主ç人" label-class-name="nowrap-label">{{ |
| | | currentMeeting.host |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®æ¶é´" :span="2" label-class-name="nowrap-label"> |
| | | {{ formatDateTime(currentMeeting.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®å°ç¹" label-class-name="nowrap-label">{{ |
| | | currentMeeting.location |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="åä¼äººæ°" label-class-name="nowrap-label">{{ |
| | | currentMeeting.participants.length |
| | | }}人</el-descriptions-item> |
| | | <el-descriptions-item label="审æ¹ç¶æ" label-class-name="nowrap-label"> |
| | | <el-tag :type="getStatusType(currentMeeting.status)"> |
| | | {{ getStatusText(currentMeeting.status) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·æ¶é´" label-class-name="nowrap-label">{{ |
| | | currentMeeting.createTime |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item style="max-height: 400px" label="ä¼è®®è¯´æ" :span="2" |
| | | label-class-name="nowrap-label">{{ currentMeeting.description }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | |
| | | <div class="content-section mt-20"> |
| | | <h4>åä¼äººå</h4> |
| | | <div class="participants-list"> |
| | | <el-tag |
| | | v-for="participant in currentMeeting.participants" |
| | | :key="participant.id" |
| | | style="margin-right: 10px; margin-bottom: 10px;" |
| | | > |
| | | {{ participant.name }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="detailDialogVisible = false">å
³ é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- ä¼è®®å®¡æ¹å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | title="ä¼è®®å®¡æ¹" |
| | | v-model="approvalDialogVisible" |
| | | > |
| | | <div v-if="currentMeeting"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="ä¼è®®ä¸»é¢">{{ currentMeeting.title }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·äºº">{{ currentMeeting.applicant }}</el-descriptions-item> |
| | | <el-descriptions-item label="主ç人">{{ currentMeeting.host }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®æ¶é´" :span="2"> |
| | | {{ formatDateTime(currentMeeting.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®å°ç¹">{{ currentMeeting.location }}</el-descriptions-item> |
| | | <el-descriptions-item label="åä¼äººæ°">{{ currentMeeting.participants.length }}人</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="content-section mt-20"> |
| | | <h4>åä¼äººå</h4> |
| | | <div class="participants-list"> |
| | | <el-tag |
| | | v-for="participant in currentMeeting.participants" |
| | | :key="participant.id" |
| | | style="margin-right: 10px; margin-bottom: 10px;" |
| | | > |
| | | {{ participant.name }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-show="false" class="approval-opinion mt-20"> |
| | | <h4>å®¡æ¹æè§</h4> |
| | | <el-input |
| | | v-model="approvalOpinion" |
| | | type="textarea" |
| | | placeholder="请è¾å
¥å®¡æ¹æè§" |
| | | :rows="4" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="approvalDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="danger" @click="submitApproval('2')">ä¸éè¿</el-button> |
| | | <el-button type="primary" @click="submitApproval('1')">é è¿</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref, reactive, onMounted} from 'vue' |
| | | import {ElMessage, ElMessageBox} from 'element-plus' |
| | | import Pagination from '@/components/Pagination/index.vue' |
| | | import {getRoomEnum, getExamineList,saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js' |
| | | import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | // æ°æ®å表å è½½ç¶æ |
| | | const loading = ref(false) |
| | | |
| | | // æ»æ¡æ° |
| | | const total = ref(0) |
| | | const roomEnum = ref([]) |
| | | const staffList = ref([]) |
| | | // 审æ¹åè¡¨æ°æ® |
| | | const approvalList = ref([]) |
| | | |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10 |
| | | }) |
| | | |
| | | // æç´¢è¡¨å |
| | | const searchForm = reactive({ |
| | | title: '', |
| | | applicant: '', |
| | | status: '' |
| | | }) |
| | | |
| | | // æ¯å¦æ¾ç¤ºå¯¹è¯æ¡ |
| | | const detailDialogVisible = ref(false) |
| | | const approvalDialogVisible = ref(false) |
| | | |
| | | // å½åæ¥ççä¼è®® |
| | | const currentMeeting = ref(null) |
| | | |
| | | // å®¡æ¹æè§ |
| | | const approvalOpinion = ref('') |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const getList = async () => { |
| | | loading.value = true |
| | | let resp = await getExamineList({...searchForm, ...queryParams}) |
| | | approvalList.value = resp.data.records.map(it => { |
| | | let room = roomEnum.value.find(room => it.roomId === room.id) |
| | | it.location = `${room.name}(${room.location})` |
| | | let staffs = JSON.parse(it.participants) |
| | | it.staffCount = staffs.size |
| | | it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}` |
| | | it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => { |
| | | return { |
| | | id: staff.id, |
| | | name: `${staff.staffName}(${staff.postJob})` |
| | | } |
| | | }) |
| | | |
| | | |
| | | return it |
| | | }) |
| | | total.value = resp.data.total |
| | | loading.value = false |
| | | } |
| | | |
| | | // æç´¢æé®æä½ |
| | | const handleSearch = () => { |
| | | queryParams.pageNum = 1 |
| | | getList() |
| | | } |
| | | |
| | | // éç½®æç´¢è¡¨å |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | title: '', |
| | | applicant: '', |
| | | status: '' |
| | | }) |
| | | handleSearch() |
| | | } |
| | | |
| | | // æ¥ç详æ
|
| | | const viewDetail = (row) => { |
| | | currentMeeting.value = row |
| | | detailDialogVisible.value = true |
| | | } |
| | | |
| | | // å¤çå®¡æ¹ |
| | | const handleApproval = (row) => { |
| | | currentMeeting.value = row |
| | | approvalOpinion.value = '' |
| | | approvalDialogVisible.value = true |
| | | } |
| | | |
| | | // è·åç¶æç±»å |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | '0': 'info', // å¾
å®¡æ¹ |
| | | '1': 'success', // å·²éè¿ |
| | | '2': 'warning', // æªéè¿ |
| | | '3': 'danger' // åæ¶ |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | // è·åç¶æææ¬ |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | '0': 'å¾
审æ¹', |
| | | '1': 'å·²éè¿', |
| | | '2': 'æªéè¿', |
| | | '3': '已忶' |
| | | } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | | const formatDateTime = (dateTime) => { |
| | | if (!dateTime) return '' |
| | | return dateTime.replace(' ', '\n') |
| | | } |
| | | |
| | | // æäº¤å®¡æ¹ |
| | | const submitApproval = (status) => { |
| | | // if (status === 'approved' && !approvalOpinion.value.trim()) { |
| | | // ElMessage.warning('请填åå®¡æ¹æè§') |
| | | // return |
| | | // } |
| | | |
| | | ElMessageBox.confirm( |
| | | `确认${status === '1' ? 'éè¿' : 'ä¸éè¿'}该ä¼è®®ç³è¯·ï¼`, |
| | | '审æ¹ç¡®è®¤', |
| | | { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | } |
| | | ).then(() => { |
| | | saveMeetingApplication({ |
| | | id: currentMeeting.value.id, |
| | | status: status |
| | | }).then(resp=>{ |
| | | // æ´æ°ä¼è®®ç¶æ |
| | | currentMeeting.value.status = status |
| | | |
| | | ElMessage.success('å®¡æ¹æäº¤æå') |
| | | approvalDialogVisible.value = false |
| | | getList() |
| | | }) |
| | | |
| | | }).catch(() => { |
| | | }) |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶è·åæ°æ® |
| | | onMounted(async () => { |
| | | const [resp1, resp2]= await Promise.all([getRoomEnum(), getStaffOnJob()]) |
| | | roomEnum.value = resp1.data |
| | | staffList.value = resp2.data |
| | | |
| | | await getList() |
| | | }) |
| | | </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; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .content-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .participants-list { |
| | | min-height: 40px; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .approval-opinion h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .nowrap-label { |
| | | white-space: nowrap !important; |
| | | } |
| | | |
| | | .description-content { |
| | | white-space: pre-wrap; |
| | | word-wrap: break-word; |
| | | line-height: 1.6; |
| | | padding: 10px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | min-height: 60px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>ä¼è®®å®¤ä½¿ç¨æ¥è¯¢</h2> |
| | | </div> |
| | | |
| | | <!-- æ¥è¯¢æ¡ä»¶ --> |
| | | <el-card class="search-card"> |
| | | <el-form :model="queryForm" label-width="80px" inline> |
| | | <el-form-item label="æ¥è¯¢æ¥æ"> |
| | | <el-date-picker |
| | | v-model="queryForm.meetingDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | :clearable="false" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æ¥è¯¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®å®¤ä½¿ç¨æ
åµ --> |
| | | <el-card class="table-container" :loading="loading"> |
| | | <div class="time-table"> |
| | | <!-- 表头 --> |
| | | <div class="table-header"> |
| | | <div class="header-cell room-header">ä¼è®®å®¤</div> |
| | | <div |
| | | v-for="timeSlot in timeSlots" |
| | | :key="timeSlot.value" |
| | | class="header-cell time-header" |
| | | > |
| | | {{ timeSlot.label }} |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è¡¨æ ¼å
容 --> |
| | | <div class="table-body"> |
| | | <div |
| | | v-for="room in roomUsage" |
| | | :key="room.id" |
| | | class="table-row" |
| | | > |
| | | <div class="cell room-cell">{{ room.name }}</div> |
| | | <div class="cells-container"> |
| | | <template v-for="(cell, index) in generateMeetingCells(room)" :key="index"> |
| | | <div |
| | | class="cell content-cell" |
| | | :class="[cell.type, `status-${cell.meeting?.status || '0'}`]" |
| | | :style="{ flex: cell.span-0.2 }" |
| | | @click="viewMeetingDetails(cell)" |
| | | > |
| | | <div v-if="cell.type === 'meeting'" class="meeting-content"> |
| | | <div class="meeting-title">{{ cell.meeting.title }}</div> |
| | | <div class="meeting-time">{{ cell.startTime }}-{{ cell.endTime }}</div> |
| | | </div> |
| | | <div v-else class="free-content"> |
| | | ç©ºé² |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®è¯¦æ
å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | title="ä¼è®®è¯¦æ
" |
| | | v-model="detailDialogVisible" |
| | | width="800px" |
| | | > |
| | | <div v-if="currentMeeting"> |
| | | <el-descriptions :column="1" border> |
| | | <el-descriptions-item label="ä¼è®®ä¸»é¢">{{ currentMeeting.title }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®å®¤">{{ currentMeeting.room }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®æ¶é´">{{ currentMeeting.time }}</el-descriptions-item> |
| | | <el-descriptions-item label="主æäºº">{{ currentMeeting.host }}</el-descriptions-item> |
| | | <el-descriptions-item label="åä¼äººæ°">{{ currentMeeting.participants }}人</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®è¯´æ">{{ currentMeeting.description }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="detailDialogVisible = false">å
³ é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref, reactive, onMounted} from 'vue' |
| | | import {ElMessage} from 'element-plus' |
| | | import {getMeetingUseList} from "@/api/collaborativeApproval/meeting.js" |
| | | import dayjs from "dayjs"; |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | | const queryForm = reactive({ |
| | | meetingDate: dayjs().format('YYYY-MM-DD') |
| | | }) |
| | | let loading = ref(false) |
| | | // æ¶é´æ®µï¼ä»¥åå°æ¶ä¸ºé´éï¼ |
| | | const timeSlots = ref([]) |
| | | |
| | | // ä¼è®®å®¤ä½¿ç¨æ
åµ |
| | | const roomUsage = ref([]) |
| | | |
| | | // å½åæ¥ççä¼è®® |
| | | const currentMeeting = ref(null) |
| | | |
| | | // æ¯å¦æ¾ç¤ºè¯¦æ
å¯¹è¯æ¡ |
| | | const detailDialogVisible = ref(false) |
| | | |
| | | // åå§åæ¶é´æ§½ï¼ä»¥åå°æ¶ä¸ºé´éï¼ä»8:00å°18:00ï¼ |
| | | const initTimeSlots = () => { |
| | | const slots = [] |
| | | for (let hour = 8; hour < 18; hour++) { |
| | | // æ¯ä¸ªå°æ¶æ·»å 两个æ¶é´æ®µï¼æ´ç¹ååç¹ |
| | | slots.push({ |
| | | label: `${hour.toString().padStart(2, '0')}:00`, |
| | | value: `${hour.toString().padStart(2, '0')}:00` |
| | | }) |
| | | |
| | | if (hour < 18) { // å°17:30ä¸ºæ¢ |
| | | slots.push({ |
| | | label: `${hour.toString().padStart(2, '0')}:30`, |
| | | value: `${hour.toString().padStart(2, '0')}:30` |
| | | }) |
| | | } |
| | | } |
| | | timeSlots.value = slots |
| | | } |
| | | |
| | | // çæä¼è®®å®¤çæ¶é´åå
æ ¼ |
| | | const generateMeetingCells = (room) => { |
| | | const cells = [] |
| | | const meetings = room.meetings || [] |
| | | const occupiedSlots = new Set() |
| | | |
| | | // å¤çæ¯ä¸ªä¼è®® |
| | | for (const meeting of meetings) { |
| | | |
| | | const startIdx = timeSlots.value.findIndex(slot => slot.value === meeting.startTime) |
| | | let endIdx = timeSlots.value.findIndex(slot => slot.value === meeting.endTime) |
| | | if (endIdx === -1) { |
| | | endIdx = timeSlots.value.length |
| | | } |
| | | console.log('endIdx111', endIdx) |
| | | if (startIdx !== -1 && endIdx !== -1) { |
| | | // æ 记被å ç¨çæ¶é´æ®µ |
| | | for (let i = startIdx; i < endIdx; i++) { |
| | | occupiedSlots.add(timeSlots.value[i].value) |
| | | } |
| | | |
| | | // å建ä¼è®®åå
æ ¼ |
| | | cells.push({ |
| | | type: 'meeting', |
| | | meeting: meeting, |
| | | span: endIdx - startIdx, |
| | | startTime: meeting.startTime, |
| | | endTime: meeting.endTime |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // å¤çç©ºé²æ¶é´æ®µ |
| | | for (let i = 0; i < timeSlots.value.length; i++) { |
| | | const slot = timeSlots.value[i] |
| | | if (!occupiedSlots.has(slot.value)) { |
| | | // æ¥æ¾è¿ç»çç©ºé²æ¶é´æ®µ |
| | | let span = 1 |
| | | while (i + span < timeSlots.value.length && |
| | | !occupiedSlots.has(timeSlots.value[i + span].value)) { |
| | | occupiedSlots.add(timeSlots.value[i + span].value) |
| | | span++ |
| | | } |
| | | |
| | | cells.push({ |
| | | type: 'free', |
| | | span: span, |
| | | time: slot.value |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // ææ¶é´æåº |
| | | cells.sort((a, b) => { |
| | | const timeA = a.startTime || a.time |
| | | const timeB = b.startTime || b.time |
| | | return timeSlots.value.findIndex(s => s.value === timeA) - |
| | | timeSlots.value.findIndex(s => s.value === timeB) |
| | | }) |
| | | console.log('cells', cells) |
| | | return cells |
| | | } |
| | | |
| | | // æ¥çä¼è®®è¯¦æ
|
| | | const viewMeetingDetails = (cell) => { |
| | | if (cell && cell.type === 'meeting') { |
| | | currentMeeting.value = cell.meeting |
| | | detailDialogVisible.value = true |
| | | } else { |
| | | ElMessage.info('该æ¶é´æ®µä¼è®®å®¤ç©ºé²') |
| | | } |
| | | } |
| | | |
| | | // æ¥è¯¢æé®æä½ |
| | | const handleSearch = async () => { |
| | | loading.value = true |
| | | let resp = await getMeetingUseList({...queryForm}) |
| | | roomUsage.value = resp.data |
| | | loading.value = false |
| | | } |
| | | |
| | | // éç½®æç´¢è¡¨å |
| | | const resetSearch = () => { |
| | | queryForm.date = dayjs().format('YYYY-MM-DD') |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶è·åæ°æ® |
| | | onMounted(() => { |
| | | // åå§åæ¶é´æ§½ |
| | | initTimeSlots() |
| | | |
| | | // é»è®¤æ¥è¯¢ä»å¤©çæ°æ® |
| | | const today = new Date() |
| | | queryForm.date = today.toISOString().split('T')[0] |
| | | handleSearch() |
| | | }) |
| | | </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; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .table-container { |
| | | padding: 0; |
| | | } |
| | | |
| | | .time-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | } |
| | | |
| | | .table-header { |
| | | display: flex; |
| | | border: 1px solid; |
| | | } |
| | | |
| | | .table-row { |
| | | display: flex; |
| | | border: 1px solid #ebeef5; |
| | | border-top: none; |
| | | } |
| | | |
| | | .header-cell { |
| | | padding: 12px 5px; |
| | | text-align: center; |
| | | font-weight: bold; |
| | | border-right: 1px solid; |
| | | min-height: 20px; |
| | | } |
| | | |
| | | .room-header { |
| | | width: 120px; |
| | | } |
| | | |
| | | .time-header { |
| | | flex: 1; |
| | | } |
| | | |
| | | .cell { |
| | | padding: 15px 5px; |
| | | text-align: center; |
| | | border-right: 1px solid; |
| | | min-height: 20px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | word-break: break-word; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | .room-cell { |
| | | width: 120px; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .cells-container { |
| | | flex: 1; |
| | | display: flex; |
| | | } |
| | | |
| | | .content-cell { |
| | | min-height: 60px; |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .content-cell:hover { |
| | | opacity: 0.8; |
| | | } |
| | | |
| | | .free { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .meeting { |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .status-1 { |
| | | background-color: #fef0f0; |
| | | color: #d14646; |
| | | } |
| | | |
| | | .status-0 { |
| | | background-color: #c7ddc8; |
| | | color: rgba(230, 162, 60, 0.29); |
| | | } |
| | | |
| | | .meeting-content { |
| | | width: 100%; |
| | | } |
| | | |
| | | .meeting-title { |
| | | font-weight: bold; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .meeting-time { |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .free-content { |
| | | color: #909399; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>ä¼è®®åå¸</h2> |
| | | </div> |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <el-card class="search-card"> |
| | | <el-form :model="searchForm" inline> |
| | | <el-form-item label="ä¼è®®ä¸»é¢"> |
| | | <el-input v-model="searchForm.title" placeholder="请è¾å
¥ä¼è®®ä¸»é¢" clearable/> |
| | | </el-form-item> |
| | | <el-form-item label="ç³è¯·äºº"> |
| | | <el-input v-model="searchForm.applicant" placeholder="请è¾å
¥ç³è¯·äºº" clearable/> |
| | | </el-form-item> |
| | | <el-form-item label="åå¸ç¶æ"> |
| | | <el-select style="width: 100px" v-model="searchForm.status" placeholder="è¯·éæ©åå¸ç¶æ" clearable> |
| | | <el-option label="å¾
åå¸" value="0"/> |
| | | <el-option label="å·²åå¸" value="1"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®åå¸å表 --> |
| | | <el-card> |
| | | <el-table v-loading="loading" :data="approvalList" border> |
| | | <el-table-column prop="title" label="ä¼è®®ä¸»é¢" align="center" min-width="200" show-overflow-tooltip/> |
| | | <el-table-column prop="applicant" label="ç³è¯·äºº" align="center" width="120"/> |
| | | <el-table-column prop="host" label="主ç人" align="center" width="120"/> |
| | | <el-table-column prop="meetingTime" label="ä¼è®®æ¶é´" align="center" width="180"> |
| | | <template #default="scope"> |
| | | {{ formatDateTime(scope.row.meetingTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="location" label="ä¼è®®å°ç¹" align="center" width="150"/> |
| | | <el-table-column prop="participants" label="åä¼äººæ°" align="center" width="100"> |
| | | <template #default="scope"> |
| | | {{ scope.row.participants.length }}人 |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" label="åå¸ç¶æ" align="center" width="120"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ getStatusText(scope.row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" align="center" width="200" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" link @click="viewDetail(scope.row)">æ¥ç</el-button> |
| | | <el-button |
| | | v-if="scope.row.status == '0'" |
| | | type="primary" |
| | | link |
| | | @click="handleApproval(scope.row)" |
| | | > |
| | | åå¸ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | v-model:page="queryParams.current" |
| | | v-model:limit="queryParams.size" |
| | | @pagination="getList" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®è¯¦æ
å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | title="ä¼è®®è¯¦æ
" |
| | | v-model="detailDialogVisible" |
| | | width="800px" |
| | | > |
| | | <div v-if="currentMeeting"> |
| | | <el-descriptions label-width="100px" class="meeting-desc" :column="2" border> |
| | | <el-descriptions-item label="ä¼è®®ä¸»é¢" label-class-name="nowrap-label">{{ |
| | | currentMeeting.title |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·äºº" label-class-name="nowrap-label">{{ |
| | | currentMeeting.applicant |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="主ç人" label-class-name="nowrap-label">{{ |
| | | currentMeeting.host |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®æ¶é´" :span="2" label-class-name="nowrap-label"> |
| | | {{ formatDateTime(currentMeeting.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®å°ç¹" label-class-name="nowrap-label">{{ |
| | | currentMeeting.location |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="åä¼äººæ°" label-class-name="nowrap-label">{{ |
| | | currentMeeting.participants.length |
| | | }}人</el-descriptions-item> |
| | | <el-descriptions-item label="åå¸ç¶æ" label-class-name="nowrap-label"> |
| | | <el-tag :type="getStatusType(currentMeeting.status)"> |
| | | {{ getStatusText(currentMeeting.status) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·æ¶é´" label-class-name="nowrap-label">{{ |
| | | currentMeeting.createTime |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item style="max-height: 400px" label="ä¼è®®è¯´æ" :span="2" |
| | | label-class-name="nowrap-label">{{ currentMeeting.description }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | |
| | | <div class="content-section mt-20"> |
| | | <h4>åä¼äººå</h4> |
| | | <div class="participants-list"> |
| | | <el-tag |
| | | v-for="participant in currentMeeting.participants" |
| | | :key="participant.id" |
| | | style="margin-right: 10px; margin-bottom: 10px;" |
| | | > |
| | | {{ participant.name }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="detailDialogVisible = false">å
³ é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- ä¼è®®åå¸å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | title="ä¼è®®åå¸" |
| | | v-model="approvalDialogVisible" |
| | | > |
| | | <div v-if="currentMeeting"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="ä¼è®®ä¸»é¢">{{ currentMeeting.title }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·äºº">{{ currentMeeting.applicant }}</el-descriptions-item> |
| | | <el-descriptions-item label="主ç人">{{ currentMeeting.host }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®æ¶é´" :span="2"> |
| | | {{ formatDateTime(currentMeeting.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®å°ç¹">{{ currentMeeting.location }}</el-descriptions-item> |
| | | <el-descriptions-item label="åä¼äººæ°">{{ currentMeeting.participants.length }}人</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="content-section mt-20"> |
| | | <h4>åä¼äººå</h4> |
| | | <div class="participants-list"> |
| | | <el-tag |
| | | v-for="participant in currentMeeting.participants" |
| | | :key="participant.id" |
| | | style="margin-right: 10px; margin-bottom: 10px;" |
| | | > |
| | | {{ participant.name }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="approval-opinion mt-20"> |
| | | <h4>å叿è§</h4> |
| | | <el-input |
| | | v-model="publishComment" |
| | | type="textarea" |
| | | placeholder="请è¾å
¥å叿è§" |
| | | :rows="4" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="approvalDialogVisible = false">å æ¶</el-button> |
| | | <!-- <el-button type="danger" @click="submitApproval('2')">ä¸éè¿</el-button>--> |
| | | <el-button type="primary" @click="submitApproval('1')">å å¸</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref, reactive, onMounted} from 'vue' |
| | | import {ElMessage, ElMessageBox} from 'element-plus' |
| | | import Pagination from '@/components/Pagination/index.vue' |
| | | import {getRoomEnum, getMeetingPublish,saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js' |
| | | import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | // æ°æ®å表å è½½ç¶æ |
| | | const loading = ref(false) |
| | | |
| | | // æ»æ¡æ° |
| | | const total = ref(0) |
| | | const roomEnum = ref([]) |
| | | const staffList = ref([]) |
| | | // åå¸åè¡¨æ°æ® |
| | | const approvalList = ref([]) |
| | | |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10 |
| | | }) |
| | | |
| | | // æç´¢è¡¨å |
| | | const searchForm = reactive({ |
| | | title: '', |
| | | applicant: '', |
| | | status: '' |
| | | }) |
| | | |
| | | // æ¯å¦æ¾ç¤ºå¯¹è¯æ¡ |
| | | const detailDialogVisible = ref(false) |
| | | const approvalDialogVisible = ref(false) |
| | | |
| | | // å½åæ¥ççä¼è®® |
| | | const currentMeeting = ref(null) |
| | | |
| | | // åå¸æè§ |
| | | const publishComment = ref('') |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const getList = async () => { |
| | | loading.value = true |
| | | let resp = await getMeetingPublish({...searchForm, ...queryParams}) |
| | | approvalList.value = resp.data.records.map(it => { |
| | | let room = roomEnum.value.find(room => it.roomId === room.id) |
| | | it.location = `${room.name}(${room.location})` |
| | | let staffs = JSON.parse(it.participants) |
| | | it.staffCount = staffs.size |
| | | it.status = it.publishStatus |
| | | it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}` |
| | | it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => { |
| | | return { |
| | | id: staff.id, |
| | | name: `${staff.staffName}(${staff.postJob})` |
| | | } |
| | | }) |
| | | |
| | | |
| | | return it |
| | | }) |
| | | total.value = resp.data.total |
| | | loading.value = false |
| | | } |
| | | |
| | | // æç´¢æé®æä½ |
| | | const handleSearch = () => { |
| | | queryParams.pageNum = 1 |
| | | getList() |
| | | } |
| | | |
| | | // éç½®æç´¢è¡¨å |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | title: '', |
| | | applicant: '', |
| | | status: '' |
| | | }) |
| | | handleSearch() |
| | | } |
| | | |
| | | // æ¥ç详æ
|
| | | const viewDetail = (row) => { |
| | | currentMeeting.value = row |
| | | detailDialogVisible.value = true |
| | | } |
| | | |
| | | // å¤çåå¸ |
| | | const handleApproval = (row) => { |
| | | currentMeeting.value = row |
| | | publishComment.value = '' |
| | | approvalDialogVisible.value = true |
| | | } |
| | | |
| | | // è·åç¶æç±»å |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | '0': 'info', // å¾
åå¸ |
| | | '1': 'success', // å·²éè¿ |
| | | '2': 'danger', // æªéè¿ |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | // è·åç¶æææ¬ |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | '0': 'å¾
åå¸', |
| | | '1': 'å·²åå¸', |
| | | '2': '已忶', |
| | | } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | | const formatDateTime = (dateTime) => { |
| | | if (!dateTime) return '' |
| | | return dateTime.replace(' ', '\n') |
| | | } |
| | | |
| | | // æäº¤åå¸ |
| | | const submitApproval = (status) => { |
| | | // if (status === 'approved' && !publishComment.value.trim()) { |
| | | // ElMessage.warning('请填åå叿è§') |
| | | // return |
| | | // } |
| | | |
| | | ElMessageBox.confirm( |
| | | `确认${status === '1' ? 'åå¸' : 'åæ¶'}该ä¼è®®ï¼`, |
| | | 'åå¸ç¡®è®¤', |
| | | { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | } |
| | | ).then(() => { |
| | | saveMeetingApplication({ |
| | | id: currentMeeting.value.id, |
| | | publishStatus: status, |
| | | publishComment: publishComment.value |
| | | }).then(resp=>{ |
| | | // æ´æ°ä¼è®®ç¶æ |
| | | currentMeeting.value.status = status |
| | | |
| | | ElMessage.success('åå¸æäº¤æå') |
| | | approvalDialogVisible.value = false |
| | | getList() |
| | | }) |
| | | |
| | | }).catch(() => { |
| | | }) |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶è·åæ°æ® |
| | | onMounted(async () => { |
| | | const [resp1, resp2]= await Promise.all([getRoomEnum(), getStaffOnJob()]) |
| | | roomEnum.value = resp1.data |
| | | staffList.value = resp2.data |
| | | |
| | | await getList() |
| | | }) |
| | | </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; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .content-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .participants-list { |
| | | min-height: 40px; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .approval-opinion h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .nowrap-label { |
| | | white-space: nowrap !important; |
| | | } |
| | | |
| | | .description-content { |
| | | white-space: pre-wrap; |
| | | word-wrap: break-word; |
| | | line-height: 1.6; |
| | | padding: 10px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | min-height: 60px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>ä¼è®®å®¤è®¾ç½®</h2> |
| | | <el-button type="primary" @click="handleAdd"> |
| | | <el-icon><Plus /></el-icon> |
| | | æ°å¢ä¼è®®å®¤ |
| | | </el-button> |
| | | </div> |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <el-card class="search-card"> |
| | | <el-form :model="searchForm" label-width="100px" inline> |
| | | <el-form-item label="ä¼è®®å®¤åç§°"> |
| | | <el-input v-model="searchForm.name" placeholder="请è¾å
¥ä¼è®®å®¤åç§°" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="ä½ç½®"> |
| | | <el-input v-model="searchForm.location" placeholder="请è¾å
¥ä½ç½®" clearable /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®å®¤å表 --> |
| | | <el-card> |
| | | <el-table v-loading="loading" :data="meetingRoomList" border> |
| | | <el-table-column prop="name" label="ä¼è®®å®¤åç§°" align="center" /> |
| | | <el-table-column prop="location" label="ä½ç½®" align="center" /> |
| | | <el-table-column prop="capacity" label="容纳人æ°" align="center" /> |
| | | <el-table-column prop="equipment" label="设å¤é
ç½®" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag v-for="item in scope.row.equipment" :key="item" style="margin-right: 5px; margin-bottom: 5px;"> |
| | | {{ item }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" label="ç¶æ" align="center" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 1 ? 'success' : 'danger'"> |
| | | {{ scope.row.status === 1 ? 'å¯ç¨' : 'ç¦ç¨' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" align="center" width="200"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" link @click="handleEdit(scope.row)">ç¼è¾</el-button> |
| | | <el-button type="danger" link @click="handleDelete(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | v-model:page="queryParams.current" |
| | | v-model:limit="queryParams.size" |
| | | @pagination="getList" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- æ·»å /ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog :title="dialogTitle" v-model="dialogVisible" width="600px" @close="cancel"> |
| | | <el-form ref="meetingRoomFormRef" :model="meetingRoomForm" :rules="rules" label-width="100px"> |
| | | <el-form-item label="ä¼è®®å®¤åç§°" prop="name"> |
| | | <el-input v-model="meetingRoomForm.name" placeholder="请è¾å
¥ä¼è®®å®¤åç§°" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä½ç½®" prop="location"> |
| | | <el-input v-model="meetingRoomForm.location" placeholder="请è¾å
¥ä¼è®®å®¤ä½ç½®" /> |
| | | </el-form-item> |
| | | <el-form-item label="容纳人æ°" prop="capacity"> |
| | | <el-input-number v-model="meetingRoomForm.capacity" :min="1" placeholder="请è¾å
¥å®¹çº³äººæ°" /> |
| | | </el-form-item> |
| | | <el-form-item label="设å¤é
ç½®" prop="equipment"> |
| | | <el-select v-model="meetingRoomForm.equipment" multiple placeholder="è¯·éæ©è®¾å¤é
ç½®" style="width: 100%"> |
| | | <el-option |
| | | v-for="item in equipmentOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-radio-group v-model="meetingRoomForm.status"> |
| | | <el-radio :label="1">å¯ç¨</el-radio> |
| | | <el-radio :label="0">ç¦ç¨</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="meetingRoomForm.remark" type="textarea" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="cancel">å æ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/Pagination/index.vue' |
| | | import {getMeetingRoomList,saveRoom,delRoom} from '@/api/collaborativeApproval/meeting.js' |
| | | |
| | | // æ°æ®å表å è½½ç¶æ |
| | | const loading = ref(false) |
| | | |
| | | // æ»æ¡æ° |
| | | const total = ref(0) |
| | | |
| | | // ä¼è®®å®¤åè¡¨æ°æ® |
| | | const meetingRoomList = ref([]) |
| | | |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10 |
| | | }) |
| | | |
| | | // æç´¢è¡¨å |
| | | const searchForm = reactive({ |
| | | name: '', |
| | | location: '' |
| | | }) |
| | | |
| | | // å¯¹è¯æ¡æ é¢ |
| | | const dialogTitle = ref('') |
| | | |
| | | // æ¯å¦æ¾ç¤ºå¯¹è¯æ¡ |
| | | const dialogVisible = ref(false) |
| | | |
| | | // 设å¤é
ç½®é项 |
| | | const equipmentOptions = ref([ |
| | | { value: 'æå½±ä»ª', label: 'æå½±ä»ª' }, |
| | | { value: 'çµè§', label: 'çµè§' }, |
| | | { value: 'é³å', label: 'é³å' }, |
| | | { value: 'çµè¯', label: 'çµè¯' }, |
| | | { value: 'è§é¢ä¼è®®ç³»ç»', label: 'è§é¢ä¼è®®ç³»ç»' }, |
| | | { value: 'ç½æ¿', label: 'ç½æ¿' }, |
| | | { value: 'ååæ¿', label: 'ååæ¿' }, |
| | | { value: 'æ 线ç½ç»', label: 'æ 线ç½ç»' } |
| | | ]) |
| | | |
| | | // è¡¨åæ°æ® |
| | | const meetingRoomForm = reactive({ |
| | | id: undefined, |
| | | name: '', |
| | | location: '', |
| | | capacity: 10, |
| | | equipment: [], |
| | | status: 1, |
| | | remark: '' |
| | | }) |
| | | |
| | | // è¡¨åæ ¡éªè§å |
| | | const rules = { |
| | | name: [{ required: true, message: 'ä¼è®®å®¤åç§°ä¸è½ä¸ºç©º', trigger: 'blur' }], |
| | | location: [{ required: true, message: 'ä½ç½®ä¸è½ä¸ºç©º', trigger: 'blur' }], |
| | | capacity: [{ required: true, message: '容纳人æ°ä¸è½ä¸ºç©º', trigger: 'blur' }] |
| | | } |
| | | |
| | | // 表åå¼ç¨ |
| | | const meetingRoomFormRef = ref(null) |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const getList = async () => { |
| | | loading.value = true |
| | | |
| | | let resp = await getMeetingRoomList({...searchForm,...queryParams}) |
| | | meetingRoomList.value = resp.data.records.map(it=>{ |
| | | it.equipment = it.equipment.split(',') |
| | | return it; |
| | | }) |
| | | total.value = resp.data.total |
| | | loading.value = false |
| | | |
| | | } |
| | | |
| | | // æç´¢æé®æä½ |
| | | const handleSearch = () => { |
| | | queryParams.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | // éç½®æç´¢è¡¨å |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | name: '', |
| | | location: '' |
| | | }) |
| | | handleSearch() |
| | | } |
| | | |
| | | // æ·»å æé®æä½ |
| | | const handleAdd = () => { |
| | | dialogTitle.value = 'æ·»å ä¼è®®å®¤' |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | // ä¿®æ¹æé®æä½ |
| | | const handleEdit = (row) => { |
| | | dialogTitle.value = 'ä¿®æ¹ä¼è®®å®¤' |
| | | Object.assign(meetingRoomForm, row) |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | // å é¤æé®æä½ |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm( |
| | | `æ¯å¦ç¡®è®¤å é¤ä¼è®®å®¤ "${row.name}"?`, |
| | | 'è¦å', |
| | | { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | } |
| | | ).then(() => { |
| | | // 模æå é¤æä½ |
| | | delRoom(row.id).then(resp=>{ |
| | | ElMessage.success('å 餿å') |
| | | getList() |
| | | }) |
| | | |
| | | }).catch(() => {}) |
| | | } |
| | | |
| | | // åæ¶æé® |
| | | const cancel = () => { |
| | | dialogVisible.value = false |
| | | reset() |
| | | } |
| | | |
| | | // 表åéç½® |
| | | const reset = () => { |
| | | Object.assign(meetingRoomForm, { |
| | | id: undefined, |
| | | name: '', |
| | | location: '', |
| | | capacity: 10, |
| | | equipment: [], |
| | | status: 1, |
| | | remark: '' |
| | | }) |
| | | meetingRoomFormRef.value?.resetFields() |
| | | } |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | meetingRoomFormRef.value?.validate((valid) => { |
| | | if (valid) { |
| | | // 模ææäº¤æä½ |
| | | |
| | | let formData = {... meetingRoomForm} |
| | | formData.equipment = formData.equipment.join(',') |
| | | saveRoom(formData).then(resp=>{ |
| | | ElMessage.success('ä¿åæå') |
| | | dialogVisible.value = false |
| | | getList() |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶è·åæ°æ® |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | </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; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>ä¼è®®çºªè¦</h2> |
| | | </div> |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <el-card class="search-card"> |
| | | <el-form :model="searchForm" inline> |
| | | <el-form-item label="ä¼è®®ä¸»é¢"> |
| | | <el-input v-model="searchForm.title" placeholder="请è¾å
¥ä¼è®®ä¸»é¢" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="ç³è¯·äºº"> |
| | | <el-input v-model="searchForm.applicant" placeholder="请è¾å
¥ç³è¯·äºº" clearable /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®å表 --> |
| | | <el-card> |
| | | <el-table v-loading="loading" :data="meetingList" border> |
| | | <el-table-column prop="title" label="ä¼è®®ä¸»é¢" align="center" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column prop="applicant" label="ç³è¯·äºº" align="center" width="120" /> |
| | | <el-table-column prop="host" label="主æäºº" align="center" width="120" /> |
| | | <el-table-column prop="meetingTime" label="ä¼è®®æ¶é´" align="center" width="180"> |
| | | <template #default="scope"> |
| | | {{ formatDateTime(scope.row.meetingTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="location" label="ä¼è®®å°ç¹" align="center" width="150" /> |
| | | <el-table-column prop="participants" label="åä¼äººæ°" align="center" width="100"> |
| | | <template #default="scope"> |
| | | {{ scope.row.participants.length }}人 |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" align="center" width="200" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" link @click="viewDetail(scope.row)">æ¥ç</el-button> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | @click="addMinutes(scope.row)" |
| | | > |
| | | æ·»å çºªè¦ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | v-model:page="queryParams.current" |
| | | v-model:limit="queryParams.size" |
| | | @pagination="getList" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®è¯¦æ
å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | title="ä¼è®®è¯¦æ
" |
| | | v-model="detailDialogVisible" |
| | | width="800px" |
| | | > |
| | | <div v-if="currentMeeting"> |
| | | <el-descriptions label-width="100px" class="meeting-desc" :column="2" border> |
| | | <el-descriptions-item label="ä¼è®®ä¸»é¢" label-class-name="nowrap-label">{{ |
| | | currentMeeting.title |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·äºº" label-class-name="nowrap-label">{{ |
| | | currentMeeting.applicant |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="主æäºº" label-class-name="nowrap-label">{{ |
| | | currentMeeting.host |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®æ¶é´" :span="2" label-class-name="nowrap-label"> |
| | | {{ formatDateTime(currentMeeting.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®å°ç¹" label-class-name="nowrap-label">{{ |
| | | currentMeeting.location |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="åä¼äººæ°" label-class-name="nowrap-label">{{ |
| | | currentMeeting.participants.length |
| | | }}人</el-descriptions-item> |
| | | <el-descriptions-item label="审æ¹ç¶æ" label-class-name="nowrap-label"> |
| | | <el-tag :type="getStatusType(currentMeeting.status)"> |
| | | {{ getStatusText(currentMeeting.status) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·æ¶é´" label-class-name="nowrap-label">{{ |
| | | currentMeeting.createTime |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item style="max-height: 400px" label="ä¼è®®è¯´æ" :span="2" |
| | | label-class-name="nowrap-label">{{ currentMeeting.description }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="content-section mt-20"> |
| | | <h4>åä¼äººå</h4> |
| | | <div class="participants-list"> |
| | | <el-tag |
| | | v-for="participant in currentMeeting.participants" |
| | | :key="participant.id" |
| | | style="margin-right: 10px; margin-bottom: 10px;" |
| | | > |
| | | {{ participant.name }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="detailDialogVisible = false">å
³ é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ·»å ä¼è®®çºªè¦å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | title="æ·»å ä¼è®®çºªè¦" |
| | | v-model="minutesDialogVisible" |
| | | width="80%" |
| | | @close="handleCloseMinutesDialog" |
| | | > |
| | | <div v-if="currentMeeting"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="ä¼è®®ä¸»é¢">{{ currentMeeting.title }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç³è¯·äºº">{{ currentMeeting.applicant }}</el-descriptions-item> |
| | | <el-descriptions-item label="主æäºº">{{ currentMeeting.host }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®æ¶é´" :span="2"> |
| | | {{ formatDateTime(currentMeeting.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¼è®®å°ç¹">{{ currentMeeting.location }}</el-descriptions-item> |
| | | <el-descriptions-item label="åä¼äººæ°">{{ currentMeeting.participants.length }}人</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="content-section mt-20"> |
| | | <h4>ä¼è®®çºªè¦å
容</h4> |
| | | <div class="editor-container"> |
| | | <Editor |
| | | v-model="minutesContent" |
| | | :min-height="400" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="minutesDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="submitMinutes">ä¿ å</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import Pagination from '@/components/Pagination/index.vue' |
| | | import Editor from '@/components/Editor/index.vue' |
| | | import { getRoomEnum, getMeetingPublish ,getMeetingMinutesByMeetingId,saveMeetingMinutes} from '@/api/collaborativeApproval/meeting.js' |
| | | import { getStaffOnJob } from "@/api/personnelManagement/onboarding.js" |
| | | import dayjs from "dayjs" |
| | | |
| | | // æ°æ®å表å è½½ç¶æ |
| | | const loading = ref(false) |
| | | |
| | | // æ»æ¡æ° |
| | | const total = ref(0) |
| | | const roomEnum = ref([]) |
| | | const staffList = ref([]) |
| | | |
| | | // ä¼è®®åè¡¨æ°æ® |
| | | const meetingList = ref([]) |
| | | |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | | current: 1, |
| | | size: 10 |
| | | }) |
| | | |
| | | // æç´¢è¡¨å |
| | | const searchForm = reactive({ |
| | | title: '', |
| | | applicant: '', |
| | | // status: '1' // é»è®¤åªæ¾ç¤ºå·²éè¿å®¡æ¹çä¼è®® |
| | | }) |
| | | |
| | | // æ¯å¦æ¾ç¤ºå¯¹è¯æ¡ |
| | | const detailDialogVisible = ref(false) |
| | | const minutesDialogVisible = ref(false) |
| | | |
| | | // å½åæ¥ççä¼è®® |
| | | const currentMeeting = ref(null) |
| | | |
| | | // ä¼è®®çºªè¦å
容 |
| | | const minutesContent = ref('') |
| | | const minutesContentId = ref('') |
| | | |
| | | // æ¥è¯¢æ°æ® |
| | | const getList = async () => { |
| | | loading.value = true |
| | | let resp = await getMeetingPublish({ ...searchForm, ...queryParams }) |
| | | meetingList.value = resp.data.records.map(it => { |
| | | let room = roomEnum.value.find(room => it.roomId === room.id) |
| | | it.location = `${room.name}(${room.location})` |
| | | let staffs = JSON.parse(it.participants) |
| | | it.staffCount = staffs.size |
| | | it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}` |
| | | it.participants = staffList.value.filter(staff => staffs.some(id => id === staff.id)).map(staff => { |
| | | return { |
| | | id: staff.id, |
| | | name: `${staff.staffName}(${staff.postJob})` |
| | | } |
| | | }) |
| | | |
| | | return it |
| | | }) |
| | | total.value = resp.data.total |
| | | loading.value = false |
| | | } |
| | | |
| | | // æç´¢æé®æä½ |
| | | const handleSearch = () => { |
| | | queryParams.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | // éç½®æç´¢è¡¨å |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { |
| | | title: '', |
| | | applicant: '', |
| | | // status: '1' |
| | | }) |
| | | handleSearch() |
| | | } |
| | | |
| | | // æ¥ç详æ
|
| | | const viewDetail = (row) => { |
| | | currentMeeting.value = row |
| | | detailDialogVisible.value = true |
| | | } |
| | | |
| | | // æ·»å ä¼è®®çºªè¦ |
| | | const addMinutes = async (row) => { |
| | | let resp = await getMeetingMinutesByMeetingId(row.id) |
| | | currentMeeting.value = row |
| | | if (resp.data){ |
| | | minutesContent.value = resp.data.content |
| | | minutesContentId.value = resp.data.id |
| | | }else { |
| | | minutesContent.value = `<h2>${row.title}ä¼è®®çºªè¦</h2> |
| | | <p><strong>ä¼è®®æ¶é´ï¼</strong>${row.meetingTime}</p> |
| | | <p><strong>ä¼è®®å°ç¹ï¼</strong>${row.location}</p> |
| | | <p><strong>主æäººï¼</strong>${row.host}</p> |
| | | <p><strong>åä¼äººåï¼</strong></p> |
| | | <ol> |
| | | ${row.participants.map(p => `<li>${p.name}</li>`).join('')} |
| | | </ol> |
| | | <p><strong>ä¼è®®å
容ï¼</strong></p> |
| | | <ol> |
| | | <li>è®®é¢ä¸ï¼ |
| | | <ul> |
| | | <li>讨论å
容ï¼</li> |
| | | <li>å³è®®äºé¡¹ï¼</li> |
| | | </ul> |
| | | </li> |
| | | <li>è®®é¢äºï¼ |
| | | <ul> |
| | | <li>讨论å
容ï¼</li> |
| | | <li>å³è®®äºé¡¹ï¼</li> |
| | | </ul> |
| | | </li> |
| | | </ol> |
| | | <p><strong>夿³¨ï¼</strong></p>` |
| | | } |
| | | |
| | | minutesDialogVisible.value = true |
| | | } |
| | | |
| | | // æäº¤ä¼è®®çºªè¦ |
| | | const submitMinutes = () => { |
| | | if (!minutesContent.value) { |
| | | ElMessage.warning('请è¾å
¥ä¼è®®çºªè¦å
容') |
| | | return |
| | | } |
| | | saveMeetingMinutes({ |
| | | id: minutesContentId.value, |
| | | content: minutesContent.value, |
| | | meetingId: currentMeeting.value.id, |
| | | title: currentMeeting.value.title |
| | | }).then(resp=>{ |
| | | console.log('ä¼è®®çºªè¦å
容:', minutesContent.value) |
| | | ElMessage.success('ä¼è®®çºªè¦ä¿åæå') |
| | | minutesDialogVisible.value = false |
| | | }) |
| | | |
| | | } |
| | | |
| | | // å
³éä¼è®®çºªè¦å¯¹è¯æ¡ |
| | | const handleCloseMinutesDialog = () => { |
| | | minutesContent.value = '' |
| | | } |
| | | |
| | | // è·åç¶æç±»å |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | '0': 'info', // å¾
å®¡æ¹ |
| | | '1': 'success', // å·²éè¿ |
| | | '2': 'warning', // æªéè¿ |
| | | '3': 'danger' // åæ¶ |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | // è·åç¶æææ¬ |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | '0': 'å¾
审æ¹', |
| | | '1': 'å·²éè¿', |
| | | '2': 'æªéè¿', |
| | | '3': '已忶' |
| | | } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | | const formatDateTime = (dateTime) => { |
| | | if (!dateTime) return '' |
| | | return dateTime.replace(' ', '\n') |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶è·åæ°æ® |
| | | onMounted(async () => { |
| | | const [resp1, resp2] = await Promise.all([getRoomEnum(), getStaffOnJob()]) |
| | | roomEnum.value = resp1.data |
| | | staffList.value = resp2.data |
| | | |
| | | await getList() |
| | | }) |
| | | </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; |
| | | } |
| | | |
| | | .search-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .content-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .participants-list { |
| | | min-height: 40px; |
| | | padding: 15px; |
| | | border-radius: 4px; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .nowrap-label { |
| | | white-space: nowrap !important; |
| | | } |
| | | |
| | | .editor-container { |
| | | border: 1px solid #dcdfe6; |
| | | border-radius: 4px; |
| | | } |
| | | </style> |
| | |
| | | </div> |
| | | <div> |
| | | <div> |
| | | <ETable :loading="tableLoading" |
| | | <PIMTable :loading="tableLoading" |
| | | :table-data="tableData" |
| | | :columns="tableColumns" |
| | | @selection-change="handleSelectionChange" |
| | |
| | | <span v-else class="no-data">--</span> |
| | | </div> |
| | | </template> |
| | | </ETable> |
| | | </PIMTable> |
| | | <el-table ref="table" :data="tableData" height="480" v-loading="tableLoading" border v-else style="width: 100%;height: calc(100vh - 25em)"> |
| | | <el-table-column label="åºå·" type="index" width="60" align="center" /> |
| | | <el-table-column prop="deviceName" label="设å¤åç§°" :show-overflow-tooltip="true"> |
| | |
| | | |
| | | // ç»ä»¶å¼å
¥ |
| | | import Pagination from "@/components/Pagination/index.vue"; |
| | | import ETable from "@/components/Table/ETable.vue"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import FormDia from "@/views/inspectionManagement/components/formDia.vue"; |
| | | import QrCodeDia from "@/views/inspectionManagement/components/qrCodeDia.vue"; |
| | | import ViewFiles from "@/views/inspectionManagement/components/viewFiles.vue"; |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">ä¾åºååç§°ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.supplierName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | @change="handleQuery" |
| | | clearable |
| | | prefix-icon="Search" |
| | | /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button |
| | | > |
| | | </div> |
| | | <div> |
| | | <!-- <el-button type="primary" @click="openForm('add')">æ°å¢</el-button> --> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <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)" |
| | | > |
| | | <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="130" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ä¾åºååç§°" |
| | | prop="supplierName" |
| | | width="250" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="产å大类" |
| | | prop="productCategory" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="è§æ ¼åå·" |
| | | prop="specificationModel" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="åä½" |
| | | prop="unit" |
| | | width="80" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="åºåºæ°é" |
| | | prop="inboundNum" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="å«ç¨åä»·(å
)" |
| | | prop="taxInclusiveUnitPrice" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="å«ç¨æ»ä»·(å
)" |
| | | prop="taxInclusiveTotalPrice" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ç¨ç(%)" |
| | | prop="taxRate" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ä¸å«ç¨æ»ä»·(å
)" |
| | | prop="taxExclusiveTotalPrice" |
| | | width="180" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="åºåºäºº" |
| | | prop="createBy" |
| | | width="80" |
| | | show-overflow-tooltip |
| | | /> |
| | | <!-- <el-table-column |
| | | fixed="right" |
| | | label="æä½" |
| | | min-width="60" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="openForm('edit', scope.row)" |
| | | >ç¼è¾</el-button |
| | | > |
| | | </template> |
| | | </el-table-column> --> |
| | | </el-table> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- æå°é¢è§å¼¹çª --> |
| | | <el-dialog |
| | | v-model="printPreviewVisible" |
| | | title="æå°é¢è§" |
| | | width="90%" |
| | | :close-on-click-modal="false" |
| | | class="print-preview-dialog" |
| | | > |
| | | <div class="print-preview-container"> |
| | | <div class="print-preview-header"> |
| | | <el-button type="primary" @click="executePrint">æ§è¡æå°</el-button> |
| | | <el-button @click="printPreviewVisible = false">å
³éé¢è§</el-button> |
| | | </div> |
| | | <div class="print-preview-content"> |
| | | <div v-if="printData.length === 0" style="text-align: center; padding: 50px; color: #999;"> |
| | | ææ æå°æ°æ® |
| | | </div> |
| | | <div v-else style="text-align: center; padding: 10px; color: #666; font-size: 14px; background: #e8f4fd; margin-bottom: 10px;"> |
| | | å
± {{ printData.length }} æ¡æ°æ®å¾
æå° |
| | | </div> |
| | | <div v-for="(item, index) in printData" :key="index" class="print-page"> |
| | | <div class="delivery-note"> |
| | | <div class="header"> |
| | | <div class="company-name">é¼è¯çå®ä¸æé责任å
¬å¸</div> |
| | | <div class="document-title">é¶å®åè´§å</div> |
| | | </div> |
| | | |
| | | <div class="info-section"> |
| | | <div class="info-row"> |
| | | <div> |
| | | <span class="label">åè´§æ¥æï¼</span> |
| | | <span class="value">{{ formatDate(item.createTime) }}</span> |
| | | </div> |
| | | <div> |
| | | |
| | | <span class="label">客æ·åç§°ï¼</span> |
| | | <span class="value">{{ item.supplierName || 'å¼ ç±æ' }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <span class="label">åå·ï¼</span> |
| | | <span class="value">{{ item.code }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table-section"> |
| | | <table class="product-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>产ååç§°</th> |
| | | <th>è§æ ¼åå·</th> |
| | | <th>åä½</th> |
| | | <th>åä»·</th> |
| | | <th>é¶å®æ°é</th> |
| | | <th>é¶å®éé¢</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr> |
| | | <td>{{ item.productCategory || 'ç ç°ç ' }}</td> |
| | | <td>{{ item.specificationModel || 'æ å' }}</td> |
| | | <td>{{ item.unit || 'å' }}</td> |
| | | <td>{{ item.taxInclusiveUnitPrice || '0' }}</td> |
| | | <td>{{ item.inboundNum || '2000' }}</td> |
| | | <td>{{ item.taxInclusiveTotalPrice || '0' }}</td> |
| | | </tr> |
| | | </tbody> |
| | | <tfoot> |
| | | <tr> |
| | | <td class="label">å计</td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value">{{ item.inboundNum || '2000' }}</td> |
| | | <td class="total-value">{{ item.taxInclusiveTotalPrice || '0' }}</td> |
| | | </tr> |
| | | </tfoot> |
| | | </table> |
| | | </div> |
| | | |
| | | <div class="footer-section"> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§çµè¯ï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§äººï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item address-item"> |
| | | <span class="label">æ¶è´§å°åï¼</span> |
| | | <span class="value address-value"></span> |
| | | </div> |
| | | </div> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æä½åï¼</span> |
| | | <span class="value">{{ userStore.nickname || 'æå¼å' }}</span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æå°æ¥æï¼</span> |
| | | <span class="value">{{ formatDateTime(new Date()) }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | |
| | | </div> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">ä¾åºååç§°ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.supplierName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | @change="handleQuery" |
| | | clearable |
| | | prefix-icon="Search" |
| | | /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button |
| | | > |
| | | </div> |
| | | <div> |
| | | <!-- <el-button type="primary" @click="openForm('add')">æ°å¢</el-button> --> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | <el-button type="primary" plain @click="handlePrint">æå°</el-button> |
| | | </div> |
| | | </div> |
| | | <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)" |
| | | > |
| | | <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" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ä¾åºååç§°" |
| | | prop="supplierName" |
| | | width="250" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="产å大类" |
| | | prop="productCategory" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="è§æ ¼åå·" |
| | | prop="specificationModel" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="åä½" |
| | | prop="unit" |
| | | width="80" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="åºåºæ°é" |
| | | prop="inboundNum" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="å«ç¨åä»·(å
)" |
| | | prop="taxInclusiveUnitPrice" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="å«ç¨æ»ä»·(å
)" |
| | | prop="taxInclusiveTotalPrice" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ç¨ç(%)" |
| | | prop="taxRate" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ä¸å«ç¨æ»ä»·(å
)" |
| | | prop="taxExclusiveTotalPrice" |
| | | width="180" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="åºåºäºº" |
| | | prop="createBy" |
| | | width="80" |
| | | show-overflow-tooltip |
| | | /> |
| | | <!-- <el-table-column |
| | | fixed="right" |
| | | label="æä½" |
| | | min-width="60" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="openForm('edit', scope.row)" |
| | | >ç¼è¾</el-button |
| | | > |
| | | </template> |
| | | </el-table-column> --> |
| | | </el-table> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- æå°é¢è§å¼¹çª --> |
| | | <el-dialog |
| | | v-model="printPreviewVisible" |
| | | title="æå°é¢è§" |
| | | width="90%" |
| | | :close-on-click-modal="false" |
| | | class="print-preview-dialog" |
| | | > |
| | | <div class="print-preview-container"> |
| | | <div class="print-preview-header"> |
| | | <el-button type="primary" @click="executePrint">æ§è¡æå°</el-button> |
| | | <el-button @click="printPreviewVisible = false">å
³éé¢è§</el-button> |
| | | </div> |
| | | <div class="print-preview-content"> |
| | | <div v-if="printData.length === 0" style="text-align: center; padding: 50px; color: #999;"> |
| | | ææ æå°æ°æ® |
| | | </div> |
| | | <div v-else style="text-align: center; padding: 10px; color: #666; font-size: 14px; background: #e8f4fd; margin-bottom: 10px;"> |
| | | å
± {{ printData.length }} æ¡æ°æ®å¾
æå° |
| | | </div> |
| | | <div v-for="(item, index) in printData" :key="index" class="print-page"> |
| | | <div class="delivery-note"> |
| | | <div class="header"> |
| | | <div class="company-name">é¼è¯çå®ä¸æé责任å
¬å¸</div> |
| | | <div class="document-title">é¶å®åè´§å</div> |
| | | </div> |
| | | |
| | | <div class="info-section"> |
| | | <div class="info-row"> |
| | | <div> |
| | | <span class="label">åè´§æ¥æï¼</span> |
| | | <span class="value">{{ formatDate(item.createTime) }}</span> |
| | | </div> |
| | | <div> |
| | | |
| | | <span class="label">客æ·åç§°ï¼</span> |
| | | <span class="value">{{ item.supplierName || 'å¼ ç±æ' }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <span class="label">åå·ï¼</span> |
| | | <span class="value">{{ item.code }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table-section"> |
| | | <table class="product-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>产ååç§°</th> |
| | | <th>è§æ ¼åå·</th> |
| | | <th>åä½</th> |
| | | <th>åä»·</th> |
| | | <th>é¶å®æ°é</th> |
| | | <th>é¶å®éé¢</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr> |
| | | <td>{{ item.productCategory || 'ç ç°ç ' }}</td> |
| | | <td>{{ item.specificationModel || 'æ å' }}</td> |
| | | <td>{{ item.unit || 'å' }}</td> |
| | | <td>{{ item.taxInclusiveUnitPrice || '0' }}</td> |
| | | <td>{{ item.inboundNum || '2000' }}</td> |
| | | <td>{{ item.taxInclusiveTotalPrice || '0' }}</td> |
| | | </tr> |
| | | </tbody> |
| | | <tfoot> |
| | | <tr> |
| | | <td class="label">å计</td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value">{{ item.inboundNum || '2000' }}</td> |
| | | <td class="total-value">{{ item.taxInclusiveTotalPrice || '0' }}</td> |
| | | </tr> |
| | | </tfoot> |
| | | </table> |
| | | </div> |
| | | |
| | | <div class="footer-section"> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§çµè¯ï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§äººï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item address-item"> |
| | | <span class="label">æ¶è´§å°åï¼</span> |
| | | <span class="value address-value"></span> |
| | | </div> |
| | | </div> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æä½åï¼</span> |
| | | <span class="value">{{ userStore.nickName || 'æå¼å' }}</span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æå°æ¥æï¼</span> |
| | | <span class="value">{{ formatDateTime(new Date()) }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | |
| | | import { ElMessageBox } from "element-plus"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { |
| | | getStockOutPage, |
| | | delStockOut, |
| | | getStockOutPage, |
| | | delStockOut, |
| | | } from "@/api/inventoryManagement/stockOut.js"; |
| | | |
| | | const userStore = useUserStore(); |
| | |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | |
| | |
| | | |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierName: "", |
| | | }, |
| | | form: { |
| | | supplierId: null, |
| | | supplierName: '', |
| | | productId: null, |
| | | productName: '', |
| | | userId: userStore.userId, |
| | | nickname: '', |
| | | model: '', |
| | | productModelId: null, |
| | | unit: '', |
| | | productrecordId: null, |
| | | taxInclusiveUnitPrice: '', |
| | | taxInclusiveTotalPrice: '', |
| | | taxRate: '', |
| | | taxExclusiveTotalPrice: '', |
| | | inboundTime: '', |
| | | inboundBatch: '', |
| | | inboundQuantity: '' |
| | | }, |
| | | searchForm: { |
| | | supplierName: "", |
| | | }, |
| | | form: { |
| | | supplierId: null, |
| | | supplierName: '', |
| | | productId: null, |
| | | productName: '', |
| | | userId: userStore.userId, |
| | | nickName: '', |
| | | model: '', |
| | | productModelId: null, |
| | | unit: '', |
| | | productrecordId: null, |
| | | taxInclusiveUnitPrice: '', |
| | | taxInclusiveTotalPrice: '', |
| | | taxRate: '', |
| | | taxExclusiveTotalPrice: '', |
| | | inboundTime: '', |
| | | inboundBatch: '', |
| | | inboundQuantity: '' |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | getStockOutPage({ ...searchForm.value, ...page }) |
| | | .then((res) => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | tableData.value.map((item) => { |
| | | item.children = []; |
| | | }); |
| | | total.value = res.data.total; |
| | | }) |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | tableLoading.value = true; |
| | | getStockOutPage({ ...searchForm.value, ...page }) |
| | | .then((res) => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | tableData.value.map((item) => { |
| | | item.children = []; |
| | | }); |
| | | total.value = res.data.total; |
| | | }) |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | // è¿æ»¤æåæ°æ® |
| | | selectedRows.value = selection.filter((item) => item.id); |
| | | console.log("selection", selectedRows.value); |
| | | // è¿æ»¤æåæ°æ® |
| | | selectedRows.value = selection.filter((item) => item.id); |
| | | console.log("selection", selectedRows.value); |
| | | }; |
| | | const expandedRowKeys = ref([]); |
| | | |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = (param) => { |
| | | return proxy.summarizeTable(param, [ |
| | | "contractAmount", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | ]); |
| | | return proxy.summarizeTable(param, [ |
| | | "contractAmount", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | ]); |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/stockmanagement/export", {}, "åºåºå°è´¦.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/stockmanagement/export", {}, "åºåºå°è´¦.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // å é¤ |
| | | 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(() => { |
| | | delStockOut({ids:ids}).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | 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(() => { |
| | | delStockOut({ids:ids}).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // æå°åè½ |
| | | const handlePrint = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©è¦æå°çæ°æ®"); |
| | | return; |
| | | } |
| | | printData.value = [...selectedRows.value]; |
| | | console.log('æå°æ°æ®:', printData.value); |
| | | printPreviewVisible.value = true; |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©è¦æå°çæ°æ®"); |
| | | return; |
| | | } |
| | | printData.value = [...selectedRows.value]; |
| | | console.log('æå°æ°æ®:', printData.value); |
| | | printPreviewVisible.value = true; |
| | | }; |
| | | |
| | | // æ§è¡æå° |
| | | const executePrint = () => { |
| | | console.log('å¼å§æ§è¡æå°ï¼æ°æ®æ¡æ°:', printData.value.length); |
| | | console.log('æå°æ°æ®:', printData.value); |
| | | |
| | | // å建ä¸ä¸ªæ°çæå°çªå£ |
| | | const printWindow = window.open('', '_blank', 'width=800,height=600'); |
| | | |
| | | // æå»ºæå°å
容 |
| | | let printContent = ` |
| | | console.log('å¼å§æ§è¡æå°ï¼æ°æ®æ¡æ°:', printData.value.length); |
| | | console.log('æå°æ°æ®:', printData.value); |
| | | |
| | | // å建ä¸ä¸ªæ°çæå°çªå£ |
| | | const printWindow = window.open('', '_blank', 'width=800,height=600'); |
| | | |
| | | // æå»ºæå°å
容 |
| | | let printContent = ` |
| | | <!DOCTYPE html> |
| | | <html> |
| | | <head> |
| | |
| | | </head> |
| | | <body> |
| | | `; |
| | | |
| | | // ä¸ºæ¯æ¡æ°æ®çææå°é¡µé¢ |
| | | printData.value.forEach((item, index) => { |
| | | printContent += ` |
| | | |
| | | // ä¸ºæ¯æ¡æ°æ®çææå°é¡µé¢ |
| | | printData.value.forEach((item, index) => { |
| | | printContent += ` |
| | | <div class="print-page"> |
| | | <div class="delivery-note"> |
| | | <div class="header"> |
| | |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æä½åï¼</span> |
| | | <span class="value">${userStore.nickname || 'æå¼å'}</span> |
| | | <span class="value">${userStore.nickName || 'æå¼å'}</span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æå°æ¥æï¼</span> |
| | |
| | | </div> |
| | | </div> |
| | | `; |
| | | }); |
| | | |
| | | printContent += ` |
| | | }); |
| | | |
| | | printContent += ` |
| | | </body> |
| | | </html> |
| | | `; |
| | | |
| | | // åå
¥å
容尿°çªå£ |
| | | printWindow.document.write(printContent); |
| | | printWindow.document.close(); |
| | | |
| | | // çå¾
å
容å è½½å®æåæå° |
| | | printWindow.onload = () => { |
| | | setTimeout(() => { |
| | | printWindow.print(); |
| | | printWindow.close(); |
| | | printPreviewVisible.value = false; |
| | | }, 500); |
| | | }; |
| | | |
| | | // åå
¥å
容尿°çªå£ |
| | | printWindow.document.write(printContent); |
| | | printWindow.document.close(); |
| | | |
| | | // çå¾
å
容å è½½å®æåæå° |
| | | printWindow.onload = () => { |
| | | setTimeout(() => { |
| | | printWindow.print(); |
| | | printWindow.close(); |
| | | printPreviewVisible.value = false; |
| | | }, 500); |
| | | }; |
| | | }; |
| | | |
| | | |
| | | |
| | | // æ ¼å¼åæ¥æ |
| | | const formatDate = (dateString) => { |
| | | if (!dateString) return getCurrentDate(); |
| | | const date = new Date(dateString); |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(date.getDate()).padStart(2, "0"); |
| | | return `${year}/${month}/${day}`; |
| | | if (!dateString) return getCurrentDate(); |
| | | const date = new Date(dateString); |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(date.getDate()).padStart(2, "0"); |
| | | return `${year}/${month}/${day}`; |
| | | }; |
| | | |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | | const formatDateTime = (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"); |
| | | const seconds = String(date.getSeconds()).padStart(2, "0"); |
| | | return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`; |
| | | 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"); |
| | | const seconds = String(date.getSeconds()).padStart(2, "0"); |
| | | return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`; |
| | | }; |
| | | // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | | const year = today.getFullYear(); |
| | | const month = String(today.getMonth() + 1).padStart(2, "0"); // æä»½ä»0å¼å§ |
| | | const day = String(today.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | const today = new Date(); |
| | | const year = today.getFullYear(); |
| | | const month = String(today.getMonth() + 1).padStart(2, "0"); // æä»½ä»0å¼å§ |
| | | const day = String(today.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | onMounted(() => { |
| | | getList(); |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .print-preview-dialog { |
| | | .el-dialog__body { |
| | | padding: 0; |
| | | max-height: 80vh; |
| | | overflow-y: auto; |
| | | } |
| | | .el-dialog__body { |
| | | padding: 0; |
| | | max-height: 80vh; |
| | | overflow-y: auto; |
| | | } |
| | | } |
| | | |
| | | .print-preview-container { |
| | | .print-preview-header { |
| | | padding: 15px; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | text-align: center; |
| | | |
| | | .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | } |
| | | |
| | | .print-preview-content { |
| | | padding: 20px; |
| | | background-color: #f5f5f5; |
| | | min-height: 400px; |
| | | } |
| | | .print-preview-header { |
| | | padding: 15px; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | text-align: center; |
| | | |
| | | .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | } |
| | | |
| | | .print-preview-content { |
| | | padding: 20px; |
| | | background-color: #f5f5f5; |
| | | min-height: 400px; |
| | | } |
| | | } |
| | | |
| | | .print-page { |
| | | width: 220mm; |
| | | height: 90mm; |
| | | padding: 10mm; |
| | | margin: 0 auto; |
| | | background: white; |
| | | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |
| | | margin-bottom: 10px; |
| | | box-sizing: border-box; |
| | | width: 220mm; |
| | | height: 90mm; |
| | | padding: 10mm; |
| | | margin: 0 auto; |
| | | background: white; |
| | | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |
| | | margin-bottom: 10px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .delivery-note { |
| | | width: 100%; |
| | | height: 100%; |
| | | font-family: "SimSun", serif; |
| | | font-size: 10px; |
| | | line-height: 1.2; |
| | | display: flex; |
| | | flex-direction: column; |
| | | width: 100%; |
| | | height: 100%; |
| | | font-family: "SimSun", serif; |
| | | font-size: 10px; |
| | | line-height: 1.2; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .header { |
| | | text-align: center; |
| | | margin-bottom: 8px; |
| | | |
| | | .company-name { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .document-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | text-align: center; |
| | | margin-bottom: 8px; |
| | | |
| | | .company-name { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .document-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | .info-section { |
| | | margin-bottom: 8px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .info-row { |
| | | line-height: 20px; |
| | | |
| | | .label { |
| | | font-weight: bold; |
| | | width: 60px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | margin-right: 20px; |
| | | min-width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | margin-bottom: 8px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .info-row { |
| | | line-height: 20px; |
| | | |
| | | .label { |
| | | font-weight: bold; |
| | | width: 60px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | margin-right: 20px; |
| | | min-width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .table-section { |
| | | margin-bottom: 4px; |
| | | flex: 1; |
| | | |
| | | .product-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | border: 1px solid #000; |
| | | |
| | | th, td { |
| | | border: 1px solid #000; |
| | | padding: 6px; |
| | | text-align: center; |
| | | font-size: 14px; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | th { |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .total-label { |
| | | text-align: right; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .total-value { |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | margin-bottom: 4px; |
| | | flex: 1; |
| | | |
| | | .product-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | border: 1px solid #000; |
| | | |
| | | th, td { |
| | | border: 1px solid #000; |
| | | padding: 6px; |
| | | text-align: center; |
| | | font-size: 14px; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | th { |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .total-label { |
| | | text-align: right; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .total-value { |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .footer-section { |
| | | .footer-row { |
| | | display: flex; |
| | | margin-bottom: 3px; |
| | | line-height: 20px; |
| | | justify-content: space-between; |
| | | |
| | | .footer-item { |
| | | display: flex; |
| | | margin-right: 20px; |
| | | |
| | | .label { |
| | | font-weight: bold; |
| | | width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | min-width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | &.address-item { |
| | | .address-value { |
| | | min-width: 200px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .footer-row { |
| | | display: flex; |
| | | margin-bottom: 3px; |
| | | line-height: 20px; |
| | | justify-content: space-between; |
| | | |
| | | .footer-item { |
| | | display: flex; |
| | | margin-right: 20px; |
| | | |
| | | .label { |
| | | font-weight: bold; |
| | | width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | min-width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | &.address-item { |
| | | .address-value { |
| | | min-width: 200px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @media print { |
| | | .app-container { |
| | | display: none; |
| | | } |
| | | |
| | | .print-page { |
| | | box-shadow: none; |
| | | margin: 0; |
| | | padding: 10mm; |
| | | padding-left: 20mm; |
| | | page-break-inside: avoid; |
| | | page-break-after: always; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | .app-container { |
| | | display: none; |
| | | } |
| | | |
| | | .print-page { |
| | | box-shadow: none; |
| | | margin: 0; |
| | | padding: 10mm; |
| | | padding-left: 20mm; |
| | | page-break-inside: avoid; |
| | | page-break-after: always; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/procurementPlan')"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon size="48" color="#9C27B0"><Calendar /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <h3>éè´è®¡å</h3> |
| | | <p>æºè½éè´è®¡åé
ç½®ï¼èªå¨è®¡ç®éè´éæ±ï¼èèåºååå®å
¨åºå</p> |
| | | <div class="card-stats"> |
| | | <span>æ´»è·è®¡å: {{ stats.activePlans }}</span> |
| | | <span>å¾
计ç®: {{ stats.pendingCalculations }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20" class="module-cards"> |
| | | <el-col :span="8"> |
| | | <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/procurementLedger')"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | |
| | | <div class="card-stats"> |
| | | <span>æ»è®¢å: {{ stats.totalOrders }}</span> |
| | | <span>æ»éé¢: Â¥{{ stats.totalAmount.toFixed(2) }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/procurementReport')"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon size="48" color="#FF6B6B"><TrendCharts /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <h3>éè´æ¥è¡¨</h3> |
| | | <p>éè´è®¢åæ§è¡æ±æ»ãæç»åæãä¸å¡ç»è®¡ãä¾åºåä¾è´§æ±æ»</p> |
| | | <div class="card-stats"> |
| | | <span>æ¥è¡¨ç±»å: 4ç§</span> |
| | | <span>æ°æ®æ´æ°: 宿¶</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import { Document, Box, Search, RefreshLeft, Money, List } from '@element-plus/icons-vue' |
| | | import { Document, Box, Search, RefreshLeft, Money, List, Calendar, TrendCharts } from '@element-plus/icons-vue' |
| | | |
| | | const router = useRouter() |
| | | |
| | |
| | | approvedReturns: 3, |
| | | activePrices: 45, |
| | | pendingPrices: 2, |
| | | activePlans: 8, |
| | | pendingCalculations: 3, |
| | | totalOrders: 30, |
| | | totalAmount: 125.8, |
| | | avgDeliveryTime: 7, |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <el-table :data="orderSummaryData" border v-loading="loading" stripe> |
| | | <el-table :data="orderSummaryData" border v-loading="loading" stripe style="width: 100%"> |
| | | <el-table-column label="订åç¼å·" prop="orderNo" width="180" fixed="left" /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" width="150" /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" min-width="150" /> |
| | | <el-table-column label="è®¢åæ¥æ" prop="orderDate" width="120" /> |
| | | <el-table-column label="计å交æ" prop="plannedDelivery" width="120" /> |
| | | <el-table-column label="å®é
交æ" prop="actualDelivery" width="120" /> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <el-table :data="orderDetailData" border v-loading="loading" stripe> |
| | | <el-table :data="orderDetailData" border v-loading="loading" stripe style="width: 100%"> |
| | | <el-table-column label="订åç¼å·" prop="orderNo" width="150" fixed="left" /> |
| | | <el-table-column label="ååç¼ç " prop="productCode" width="120" /> |
| | | <el-table-column label="åååç§°" prop="productName" width="200" /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specification" width="150" /> |
| | | <el-table-column label="åååç§°" prop="productName" min-width="200" /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specification" min-width="150" /> |
| | | <el-table-column label="åä½" prop="unit" width="80" /> |
| | | <el-table-column label="è®¡åæ°é" prop="plannedQuantity" width="100" /> |
| | | <el-table-column label="å·²æ¶è´§æ°é" prop="receivedQuantity" width="120" /> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <el-table :data="businessSummaryData" border v-loading="loading" stripe> |
| | | <el-table :data="businessSummaryData" border v-loading="loading" stripe style="width: 100%"> |
| | | <el-table-column label="ååç±»å«" prop="category" width="150" fixed="left" /> |
| | | <el-table-column label="ååç¼ç " prop="productCode" width="120" /> |
| | | <el-table-column label="åååç§°" prop="productName" width="200" /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specification" width="150" /> |
| | | <el-table-column label="åååç§°" prop="productName" min-width="200" /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specification" min-width="150" /> |
| | | <el-table-column label="éè´æ°é" prop="purchaseQuantity" width="120" /> |
| | | <el-table-column label="éè´éé¢" prop="purchaseAmount" width="120"> |
| | | <template #default="{ row }">Â¥{{ row.purchaseAmount.toLocaleString() }}</template> |
| | |
| | | <template #default="{ row }">Â¥{{ row.avgPrice.toFixed(2) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="éè´æ¬¡æ°" prop="purchaseCount" width="100" /> |
| | | <el-table-column label="主è¦ä¾åºå" prop="mainSupplier" width="150" /> |
| | | <el-table-column label="主è¦ä¾åºå" prop="mainSupplier" min-width="150" /> |
| | | <el-table-column label="æåéè´æ¥æ" prop="lastPurchaseDate" width="120" /> |
| | | </el-table> |
| | | </div> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <el-table :data="supplierSummaryData" border v-loading="loading" stripe> |
| | | <el-table :data="supplierSummaryData" border v-loading="loading" stripe style="width: 100%"> |
| | | <el-table-column label="ä¾åºåç¼ç " prop="supplierCode" width="120" fixed="left" /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" width="200" /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" min-width="200" /> |
| | | <el-table-column label="è系人" prop="contactPerson" width="120" /> |
| | | <el-table-column label="èç³»çµè¯" prop="phone" width="130" /> |
| | | <el-table-column label="ä¾è´§è®¢åæ°" prop="orderCount" width="120" /> |
| | |
| | | :deep(.el-table) { |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | width: 100% !important; |
| | | } |
| | | |
| | | :deep(.el-table__body-wrapper) { |
| | | width: 100% !important; |
| | | } |
| | | |
| | | :deep(.el-table__header-wrapper) { |
| | | width: 100% !important; |
| | | } |
| | | |
| | | :deep(.el-table th) { |
| | |
| | | </el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | <el-button type="primary" plain @click="handlePrint">æå°</el-button> |
| | | </div> |
| | | </div> |
| | | <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" |
| | |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- æå°é¢è§å¼¹çª --> |
| | | <el-dialog |
| | | v-model="printPreviewVisible" |
| | | title="æå°é¢è§" |
| | | width="90%" |
| | | :close-on-click-modal="false" |
| | | class="print-preview-dialog" |
| | | > |
| | | <div class="print-preview-container"> |
| | | <div class="print-preview-header"> |
| | | <el-button type="primary" @click="executePrint">æ§è¡æå°</el-button> |
| | | <el-button @click="printPreviewVisible = false">å
³éé¢è§</el-button> |
| | | </div> |
| | | <div class="print-preview-content"> |
| | | <div v-if="printData.length === 0" style="text-align: center; padding: 50px; color: #999;"> |
| | | ææ æå°æ°æ® |
| | | </div> |
| | | <div v-else style="text-align: center; padding: 10px; color: #666; font-size: 14px; background: #e8f4fd; margin-bottom: 10px;"> |
| | | å
± {{ printData.length }} æ¡æ°æ®å¾
æå° |
| | | </div> |
| | | <div v-for="(item, index) in printData" :key="index" class="print-page"> |
| | | <div class="delivery-note"> |
| | | <div class="header"> |
| | | <div class="company-name">é¼è¯çå®ä¸æé责任å
¬å¸</div> |
| | | <div class="document-title">é¶å®åè´§å</div> |
| | | </div> |
| | | |
| | | <div class="info-section"> |
| | | <div class="info-row"> |
| | | <div> |
| | | <span class="label">åè´§æ¥æï¼</span> |
| | | <span class="value">{{ formatDate(item.createTime) }}</span> |
| | | </div> |
| | | <div> |
| | | |
| | | <span class="label">客æ·åç§°ï¼</span> |
| | | <span class="value">{{ item.customerName || 'å¼ ç±æ' }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <span class="label">åå·ï¼</span> |
| | | <span class="value">{{ item.salesContractNo }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table-section"> |
| | | <table class="product-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>产ååç§°</th> |
| | | <th>è§æ ¼åå·</th> |
| | | <th>åä½</th> |
| | | <th>åä»·</th> |
| | | <th>é¶å®æ°é</th> |
| | | <th>é¶å®éé¢</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr v-for="product in item.products" :key="product.id"> |
| | | <td>{{ product.productCategory || '' }}</td> |
| | | <td>{{ product.specificationModel || '' }}</td> |
| | | <td>{{ product.unit || '' }}</td> |
| | | <td>{{ product.taxInclusiveUnitPrice || '0' }}</td> |
| | | <td>{{ product.quantity || '0' }}</td> |
| | | <td>{{ product.taxInclusiveTotalPrice || '0' }}</td> |
| | | </tr> |
| | | <tr v-if="!item.products || item.products.length === 0"> |
| | | <td colspan="6" style="text-align: center; color: #999;">ææ äº§åæ°æ®</td> |
| | | </tr> |
| | | </tbody> |
| | | <tfoot> |
| | | <tr> |
| | | <td class="label">å计</td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value">{{ getTotalQuantity(item.products) }}</td> |
| | | <td class="total-value">{{ getTotalAmount(item.products) }}</td> |
| | | </tr> |
| | | </tfoot> |
| | | </table> |
| | | </div> |
| | | |
| | | <div class="footer-section"> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§çµè¯ï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§äººï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item address-item"> |
| | | <span class="label">æ¶è´§å°åï¼</span> |
| | | <span class="value address-value"></span> |
| | | </div> |
| | | </div> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æä½åï¼</span> |
| | | <span class="value">{{ userStore.nickName || 'æå¼å' }}</span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æå°æ¥æï¼</span> |
| | | <span class="value">{{ formatDateTime(new Date()) }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | <FileList ref="fileListRef" /> |
| | | </div> |
| | | </template> |
| | |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | }); |
| | | // æå°ç¸å
³ |
| | | const printPreviewVisible = ref(false); |
| | | const printData = ref([]); |
| | | |
| | | const changeDaterange = (value) => { |
| | | if (value) { |
| | |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // æå°åè½ |
| | | const handlePrint = async () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©è¦æå°çæ°æ®"); |
| | | return; |
| | | } |
| | | |
| | | // æ¾ç¤ºå è½½ç¶æ |
| | | proxy.$modal.loading("æ£å¨è·åäº§åæ°æ®ï¼è¯·ç¨å..."); |
| | | |
| | | try { |
| | | // 为æ¯ä¸ªéä¸çéå®å°è´¦è®°å½æ¥è¯¢å¯¹åºçäº§åæ°æ® |
| | | const printDataWithProducts = []; |
| | | |
| | | for (const row of selectedRows.value) { |
| | | try { |
| | | // è°ç¨productListæ¥å£æ¥è¯¢äº§åæ°æ® |
| | | const productRes = await productList({ salesLedgerId: row.id, type: 1 }); |
| | | |
| | | // å°äº§åæ°æ®æ´åå°éå®å°è´¦è®°å½ä¸ |
| | | const rowWithProducts = { |
| | | ...row, |
| | | products: productRes.data || [] |
| | | }; |
| | | |
| | | printDataWithProducts.push(rowWithProducts); |
| | | } catch (error) { |
| | | console.error(`è·åéå®å°è´¦ ${row.id} çäº§åæ°æ®å¤±è´¥:`, error); |
| | | // å³ä½¿æä¸ªè®°å½çäº§åæ°æ®è·å失败ï¼ä¹è¦å
å«è¯¥è®°å½ |
| | | printDataWithProducts.push({ |
| | | ...row, |
| | | products: [] |
| | | }); |
| | | } |
| | | } |
| | | |
| | | printData.value = printDataWithProducts; |
| | | console.log('æå°æ°æ®ï¼å
å«äº§åï¼:', printData.value); |
| | | printPreviewVisible.value = true; |
| | | |
| | | } catch (error) { |
| | | console.error('è·åäº§åæ°æ®å¤±è´¥:', error); |
| | | proxy.$modal.msgError("è·åäº§åæ°æ®å¤±è´¥ï¼è¯·éè¯"); |
| | | } finally { |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | }; |
| | | // æ§è¡æå° |
| | | const executePrint = () => { |
| | | console.log('å¼å§æ§è¡æå°ï¼æ°æ®æ¡æ°:', printData.value.length); |
| | | console.log('æå°æ°æ®:', printData.value); |
| | | |
| | | // å建ä¸ä¸ªæ°çæå°çªå£ |
| | | const printWindow = window.open('', '_blank', 'width=800,height=600'); |
| | | |
| | | // æå»ºæå°å
容 |
| | | let printContent = ` |
| | | <!DOCTYPE html> |
| | | <html> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <title>æå°é¢è§</title> |
| | | <style> |
| | | body { |
| | | margin: 0; |
| | | padding: 0; |
| | | font-family: "SimSun", serif; |
| | | background: white; |
| | | } |
| | | .print-page { |
| | | width: 200mm; |
| | | height: 75mm; |
| | | padding: 10mm; |
| | | padding-left: 20mm; |
| | | background: white; |
| | | box-sizing: border-box; |
| | | page-break-after: always; |
| | | page-break-inside: avoid; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | .delivery-note { |
| | | width: 100%; |
| | | height: 100%; |
| | | font-size: 12px; |
| | | line-height: 1.2; |
| | | display: flex; |
| | | flex-direction: column; |
| | | color: #000; |
| | | } |
| | | .header { |
| | | text-align: center; |
| | | margin-bottom: 8px; |
| | | } |
| | | .company-name { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | margin-bottom: 4px; |
| | | } |
| | | .document-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | .info-section { |
| | | margin-bottom: 8px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .info-row { |
| | | line-height: 20px; |
| | | } |
| | | .label { |
| | | font-weight: bold; |
| | | width: 60px; |
| | | font-size: 12px; |
| | | } |
| | | .value { |
| | | margin-right: 20px; |
| | | min-width: 80px; |
| | | font-size: 12px; |
| | | } |
| | | .table-section { |
| | | margin-bottom: 40px; |
| | | // flex: 0.6; |
| | | } |
| | | .product-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | border: 1px solid #000; |
| | | } |
| | | .product-table th, .product-table td { |
| | | border: 1px solid #000; |
| | | padding: 6px; |
| | | text-align: center; |
| | | font-size: 12px; |
| | | line-height: 1.4; |
| | | } |
| | | .product-table th { |
| | | font-weight: bold; |
| | | } |
| | | .total-value { |
| | | font-weight: bold; |
| | | } |
| | | .footer-section { |
| | | margin-top: auto; |
| | | } |
| | | .footer-row { |
| | | display: flex; |
| | | margin-bottom: 3px; |
| | | line-height: 22px; |
| | | justify-content: space-between; |
| | | } |
| | | .footer-item { |
| | | display: flex; |
| | | margin-right: 20px; |
| | | } |
| | | .footer-item .label { |
| | | font-weight: bold; |
| | | width: 80px; |
| | | font-size: 12px; |
| | | } |
| | | .footer-item .value { |
| | | min-width: 80px; |
| | | font-size: 12px; |
| | | } |
| | | .address-item .address-value { |
| | | min-width: 200px; |
| | | } |
| | | @media print { |
| | | body { |
| | | margin: 0; |
| | | padding: 0; |
| | | } |
| | | .print-page { |
| | | margin: 0; |
| | | padding: 10mm; |
| | | /* padding-left: 20mm; */ |
| | | page-break-inside: avoid; |
| | | page-break-after: always; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | `; |
| | | |
| | | // ä¸ºæ¯æ¡æ°æ®çææå°é¡µé¢ |
| | | printData.value.forEach((item, index) => { |
| | | printContent += ` |
| | | <div class="print-page"> |
| | | <div class="delivery-note"> |
| | | <div class="header"> |
| | | <div class="company-name">é¼è¯çå®ä¸æé责任å
¬å¸</div> |
| | | <div class="document-title">é¶å®åè´§å</div> |
| | | </div> |
| | | |
| | | <div class="info-section"> |
| | | <div class="info-row"> |
| | | <div> |
| | | <span class="label">åè´§æ¥æï¼</span> |
| | | <span class="value">${formatDate(item.createTime)}</span> |
| | | </div> |
| | | <div> |
| | | <span class="label">客æ·åç§°ï¼</span> |
| | | <span class="value">${item.customerName || 'å¼ ç±æ'}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <span class="label">åå·ï¼</span> |
| | | <span class="value">${item.salesContractNo || ''}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table-section"> |
| | | <table class="product-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>产ååç§°</th> |
| | | <th>è§æ ¼åå·</th> |
| | | <th>åä½</th> |
| | | <th>åä»·</th> |
| | | <th>é¶å®æ°é</th> |
| | | <th>é¶å®éé¢</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | ${item.products && item.products.length > 0 ? |
| | | item.products.map(product => ` |
| | | <tr> |
| | | <td>${product.productCategory || ''}</td> |
| | | <td>${product.specificationModel || ''}</td> |
| | | <td>${product.unit || ''}</td> |
| | | <td>${product.taxInclusiveUnitPrice || '0'}</td> |
| | | <td>${product.quantity || '0'}</td> |
| | | <td>${product.taxInclusiveTotalPrice || '0'}</td> |
| | | </tr> |
| | | `).join('') : |
| | | '<tr><td colspan="6" style="text-align: center; color: #999;">ææ äº§åæ°æ®</td></tr>' |
| | | } |
| | | </tbody> |
| | | <tfoot> |
| | | <tr> |
| | | <td class="label">å计</td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value">${getTotalQuantityForPrint(item.products)}</td> |
| | | <td class="total-value">${getTotalAmountForPrint(item.products)}</td> |
| | | </tr> |
| | | </tfoot> |
| | | </table> |
| | | </div> |
| | | |
| | | <div class="footer-section"> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§çµè¯ï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§äººï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item address-item"> |
| | | <span class="label">æ¶è´§å°åï¼</span> |
| | | <span class="value address-value"></span> |
| | | </div> |
| | | </div> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æä½åï¼</span> |
| | | <span class="value">${userStore.nickName || 'æå¼å'}</span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æå°æ¥æï¼</span> |
| | | <span class="value">${formatDateTime(new Date())}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | `; |
| | | }); |
| | | |
| | | printContent += ` |
| | | </body> |
| | | </html> |
| | | `; |
| | | |
| | | // åå
¥å
容尿°çªå£ |
| | | printWindow.document.write(printContent); |
| | | printWindow.document.close(); |
| | | |
| | | // çå¾
å
容å è½½å®æåæå° |
| | | printWindow.onload = () => { |
| | | setTimeout(() => { |
| | | printWindow.print(); |
| | | printWindow.close(); |
| | | printPreviewVisible.value = false; |
| | | }, 500); |
| | | }; |
| | | }; |
| | | // æ ¼å¼åæ¥æ |
| | | const formatDate = (dateString) => { |
| | | if (!dateString) return getCurrentDate(); |
| | | const date = new Date(dateString); |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(date.getDate()).padStart(2, "0"); |
| | | return `${year}/${month}/${day}`; |
| | | }; |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | | const formatDateTime = (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"); |
| | | const seconds = String(date.getSeconds()).padStart(2, "0"); |
| | | return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`; |
| | | }; |
| | | // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | |
| | | const day = String(today.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | |
| | | // 计ç®äº§åæ»æ°é |
| | | const getTotalQuantity = (products) => { |
| | | if (!products || products.length === 0) return '0'; |
| | | const total = products.reduce((sum, product) => { |
| | | return sum + (parseFloat(product.quantity) || 0); |
| | | }, 0); |
| | | return total.toFixed(2); |
| | | }; |
| | | |
| | | // 计ç®äº§åæ»éé¢ |
| | | const getTotalAmount = (products) => { |
| | | if (!products || products.length === 0) return '0'; |
| | | const total = products.reduce((sum, product) => { |
| | | return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0); |
| | | }, 0); |
| | | return total.toFixed(2); |
| | | }; |
| | | |
| | | // ç¨äºæå°ç计ç®å½æ° |
| | | const getTotalQuantityForPrint = (products) => { |
| | | if (!products || products.length === 0) return '0'; |
| | | const total = products.reduce((sum, product) => { |
| | | return sum + (parseFloat(product.quantity) || 0); |
| | | }, 0); |
| | | return total.toFixed(2); |
| | | }; |
| | | |
| | | const getTotalAmountForPrint = (products) => { |
| | | if (!products || products.length === 0) return '0'; |
| | | const total = products.reduce((sum, product) => { |
| | | return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0); |
| | | }, 0); |
| | | return total.toFixed(2); |
| | | }; |
| | | |
| | | const mathNum = () => { |
| | | console.log("productForm.value", productForm.value); |
| | |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | .print-preview-dialog { |
| | | .el-dialog__body { |
| | | padding: 0; |
| | | max-height: 80vh; |
| | | overflow-y: auto; |
| | | } |
| | | } |
| | | |
| | | .print-preview-container { |
| | | .print-preview-header { |
| | | padding: 15px; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | text-align: center; |
| | | |
| | | .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | } |
| | | |
| | | .print-preview-content { |
| | | padding: 20px; |
| | | background-color: #f5f5f5; |
| | | min-height: 400px; |
| | | } |
| | | } |
| | | |
| | | .print-page { |
| | | width: 220mm; |
| | | height: 90mm; |
| | | padding: 10mm; |
| | | margin: 0 auto; |
| | | background: white; |
| | | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |
| | | margin-bottom: 10px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .delivery-note { |
| | | width: 100%; |
| | | height: 100%; |
| | | font-family: "SimSun", serif; |
| | | font-size: 10px; |
| | | line-height: 1.2; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .header { |
| | | text-align: center; |
| | | margin-bottom: 8px; |
| | | |
| | | .company-name { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .document-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | .info-section { |
| | | margin-bottom: 8px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .info-row { |
| | | line-height: 20px; |
| | | |
| | | .label { |
| | | font-weight: bold; |
| | | width: 60px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | margin-right: 20px; |
| | | min-width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .table-section { |
| | | margin-bottom: 4px; |
| | | flex: 1; |
| | | |
| | | .product-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | border: 1px solid #000; |
| | | |
| | | th, td { |
| | | border: 1px solid #000; |
| | | padding: 6px; |
| | | text-align: center; |
| | | font-size: 14px; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | th { |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .total-label { |
| | | text-align: right; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .total-value { |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .footer-section { |
| | | .footer-row { |
| | | display: flex; |
| | | margin-bottom: 3px; |
| | | line-height: 20px; |
| | | justify-content: space-between; |
| | | |
| | | .footer-item { |
| | | display: flex; |
| | | margin-right: 20px; |
| | | |
| | | .label { |
| | | font-weight: bold; |
| | | width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | min-width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | &.address-item { |
| | | .address-value { |
| | | min-width: 200px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @media print { |
| | | .app-container { |
| | | display: none; |
| | | } |
| | | |
| | | .print-page { |
| | | box-shadow: none; |
| | | margin: 0; |
| | | padding: 10mm; |
| | | padding-left: 20mm; |
| | | page-break-inside: avoid; |
| | | page-break-after: always; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <!-- æç´¢åºå --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="6"> |
| | | <el-input |
| | | v-model="searchForm.quotationNo" |
| | | placeholder="请è¾å
¥æ¥ä»·åå·" |
| | | clearable |
| | | @keyup.enter="handleSearch" |
| | | > |
| | | <template #prefix> |
| | | <el-icon><Search /></el-icon> |
| | | </template> |
| | | </el-input> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.customer" placeholder="è¯·éæ©å®¢æ·" clearable> |
| | | <el-option label="䏿µ·ç§ææéå
¬å¸" value="䏿µ·ç§ææéå
¬å¸"></el-option> |
| | | <el-option label="æ·±å³çµåæéå
¬å¸" value="æ·±å³çµåæéå
¬å¸"></el-option> |
| | | <el-option label="å京贸æå
¬å¸" value="å京贸æå
¬å¸"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©æ¥ä»·ç¶æ" clearable> |
| | | <el-option label="è稿" value="è稿"></el-option> |
| | | <el-option label="å·²åé" value="å·²åé"></el-option> |
| | | <el-option label="客æ·ç¡®è®¤" value="客æ·ç¡®è®¤"></el-option> |
| | | <el-option label="å·²è¿æ" value="å·²è¿æ"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | <el-button style="float: right;" type="primary" @click="handleAdd"> |
| | | æ°å¢æ¥ä»· |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- æ¥ä»·å表 --> |
| | | <el-table |
| | | :data="filteredList" |
| | | style="width: 100%" |
| | | v-loading="loading" |
| | | border |
| | | stripe |
| | | height="calc(100vh - 22em)" |
| | | > |
| | | <el-table-column prop="id" label="ID" width="80" align="center"/> |
| | | <el-table-column prop="quotationNo" label="æ¥ä»·åå·" width="150" /> |
| | | <el-table-column prop="customer" label="客æ·åç§°" /> |
| | | <el-table-column prop="salesperson" label="ä¸å¡å" width="100" /> |
| | | <el-table-column prop="quotationDate" label="æ¥ä»·æ¥æ" width="120" /> |
| | | <el-table-column prop="validDate" label="æææè³" width="120" /> |
| | | <el-table-column prop="totalAmount" label="æ¥ä»·éé¢" width="120"> |
| | | <template #default="scope"> |
| | | ¥{{ scope.row.totalAmount.toFixed(2) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" label="æ¥ä»·ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="250" fixed="right" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleView(scope.row)">æ¥ç</el-button> |
| | | <el-button link type="primary" @click="handleEdit(scope.row)" v-if="scope.row.status === 'è稿'">ç¼è¾</el-button> |
| | | <el-button link type="danger" @click="handleDelete(scope.row)" v-if="scope.row.status === 'è稿'">å é¤</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 v-model="dialogVisible" :title="dialogTitle" width="900px" :close-on-click-modal="false"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <!-- åºæ¬ä¿¡æ¯ --> |
| | | <el-card class="form-card" shadow="never"> |
| | | <template #header> |
| | | <span class="card-title">åºæ¬ä¿¡æ¯</span> |
| | | </template> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç§°" prop="customer"> |
| | | <el-select v-model="form.customer" placeholder="è¯·éæ©å®¢æ·" style="width: 100%" @change="handleCustomerChange"> |
| | | <el-option label="䏿µ·ç§ææéå
¬å¸" value="䏿µ·ç§ææéå
¬å¸"></el-option> |
| | | <el-option label="æ·±å³çµåæéå
¬å¸" value="æ·±å³çµåæéå
¬å¸"></el-option> |
| | | <el-option label="å京贸æå
¬å¸" value="å京贸æå
¬å¸"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¸å¡å" prop="salesperson"> |
| | | <el-select v-model="form.salesperson" placeholder="è¯·éæ©ä¸å¡å" style="width: 100%"> |
| | | <el-option label="éå¿å¼º" value="éå¿å¼º"></el-option> |
| | | <el-option label="åé
å©·" value="åé
å©·"></el-option> |
| | | <el-option label="ç建å½" value="ç建å½"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¥ä»·æ¥æ" prop="quotationDate"> |
| | | <el-date-picker |
| | | v-model="form.quotationDate" |
| | | type="date" |
| | | placeholder="éæ©æ¥ä»·æ¥æ" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æææè³" prop="validDate"> |
| | | <el-date-picker |
| | | v-model="form.validDate" |
| | | type="date" |
| | | placeholder="éæ©æææ" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> |
| | | <el-select v-model="form.paymentMethod" placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" style="width: 100%"> |
| | | <el-option label="å
¨æ¬¾å°ä»" value="å
¨æ¬¾å°ä»"></el-option> |
| | | <el-option label="åæä»æ¬¾" value="åæä»æ¬¾"></el-option> |
| | | <el-option label="æç»" value="æç»"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="交货æ" prop="deliveryPeriod"> |
| | | <el-input v-model="form.deliveryPeriod" placeholder="请è¾å
¥äº¤è´§æ" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-card> |
| | | |
| | | <!-- 产åä¿¡æ¯ --> |
| | | <el-card class="form-card" shadow="never"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span class="card-title">产åä¿¡æ¯</span> |
| | | <el-button type="primary" size="small" @click="addProduct">æ·»å 产å</el-button> |
| | | </div> |
| | | </template> |
| | | <el-table :data="form.products" border style="width: 100%"> |
| | | <el-table-column prop="productName" label="产ååç§°" width="200"> |
| | | <template #default="scope"> |
| | | <el-input v-model="scope.row.productName" placeholder="请è¾å
¥äº§ååç§°" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="specification" label="è§æ ¼åå·" width="150"> |
| | | <template #default="scope"> |
| | | <el-input v-model="scope.row.specification" placeholder="è§æ ¼åå·" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="quantity" label="æ°é" width="100"> |
| | | <template #default="scope"> |
| | | <el-input-number v-model="scope.row.quantity" :min="1" :precision="0" style="width: 100%" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unit" label="åä½" width="80"> |
| | | <template #default="scope"> |
| | | <el-input v-model="scope.row.unit" placeholder="åä½" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unitPrice" label="åä»·" width="120"> |
| | | <template #default="scope"> |
| | | <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" @change="calculateAmount(scope.row)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="amount" label="éé¢" width="120"> |
| | | <template #default="scope"> |
| | | <span>Â¥{{ scope.row.amount.toFixed(2) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="80" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="danger" @click="removeProduct(scope.$index)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <!-- è´¹ç¨ä¿¡æ¯ --> |
| | | <el-card class="form-card" shadow="never"> |
| | | <template #header> |
| | | <span class="card-title">è´¹ç¨ä¿¡æ¯</span> |
| | | </template> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="产åå°è®¡"> |
| | | <el-input-number v-model="form.subtotal" :precision="2" :min="0" style="width: 100%" readonly /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="è¿è´¹"> |
| | | <el-input-number v-model="form.freight" :precision="2" :min="0" style="width: 100%" @change="calculateTotal" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="å
¶ä»è´¹ç¨"> |
| | | <el-input-number v-model="form.otherFee" :precision="2" :min="0" style="width: 100%" @change="calculateTotal" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="ææ£ç(%)"> |
| | | <el-input-number v-model="form.discountRate" :precision="2" :min="0" :max="100" style="width: 100%" @change="calculateTotal" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="ææ£éé¢"> |
| | | <el-input-number v-model="form.discountAmount" :precision="2" :min="0" style="width: 100%" readonly /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="æ¥ä»·æ»é¢"> |
| | | <el-input-number v-model="form.totalAmount" :precision="2" :min="0" style="width: 100%" readonly /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-card> |
| | | |
| | | <!-- 夿³¨ä¿¡æ¯ --> |
| | | <el-card class="form-card" shadow="never"> |
| | | <template #header> |
| | | <span class="card-title">夿³¨ä¿¡æ¯</span> |
| | | </template> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input type="textarea" v-model="form.remark" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" rows="3"></el-input> |
| | | </el-form-item> |
| | | </el-card> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ¥ç详æ
å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="viewDialogVisible" title="æ¥ä»·è¯¦æ
" width="800px"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="æ¥ä»·åå·">{{ currentQuotation.quotationNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="客æ·åç§°">{{ currentQuotation.customer }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¸å¡å">{{ currentQuotation.salesperson }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¥ä»·æ¥æ">{{ currentQuotation.quotationDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="æææè³">{{ currentQuotation.validDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="仿¬¾æ¹å¼">{{ currentQuotation.paymentMethod }}</el-descriptions-item> |
| | | <el-descriptions-item label="交货æ">{{ currentQuotation.deliveryPeriod }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¥ä»·ç¶æ"> |
| | | <el-tag :type="getStatusType(currentQuotation.status)">{{ currentQuotation.status }}</el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="æ¥ä»·æ»é¢" :span="2"> |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">Â¥{{ currentQuotation.totalAmount?.toFixed(2) }}</span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <h4>产åæç»</h4> |
| | | <el-table :data="currentQuotation.products" border style="width: 100%"> |
| | | <el-table-column prop="productName" label="产ååç§°" /> |
| | | <el-table-column prop="specification" label="è§æ ¼åå·" /> |
| | | <el-table-column prop="quantity" label="æ°é" /> |
| | | <el-table-column prop="unit" label="åä½" /> |
| | | <el-table-column prop="unitPrice" label="åä»·"> |
| | | <template #default="scope"> |
| | | ¥{{ scope.row.unitPrice.toFixed(2) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="amount" label="éé¢"> |
| | | <template #default="scope"> |
| | | ¥{{ scope.row.amount.toFixed(2) }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div v-if="currentQuotation.remark" style="margin-top: 20px;"> |
| | | <h4>夿³¨</h4> |
| | | <p>{{ currentQuotation.remark }}</p> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Search } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/PIMTable/Pagination.vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | | const searchForm = reactive({ |
| | | quotationNo: '', |
| | | customer: '', |
| | | status: '' |
| | | }) |
| | | |
| | | const quotationList = ref([ |
| | | { |
| | | id: 1, |
| | | quotationNo: 'QT202312001', |
| | | customer: '䏿µ·ç§ææéå
¬å¸', |
| | | salesperson: 'éå¿å¼º', |
| | | quotationDate: '2023-12-01', |
| | | validDate: '2023-12-31', |
| | | totalAmount: 50000.00, |
| | | paymentMethod: 'å
¨æ¬¾å°ä»', |
| | | deliveryPeriod: '30天', |
| | | status: 'å·²åé', |
| | | remark: 'éè¦å®¢æ·æ¥ä»·', |
| | | products: [ |
| | | { productName: 'å·¥ä¸ä¼ æå¨', specification: 'SEN-001', quantity: 10, unit: '个', unitPrice: 5000, amount: 50000 } |
| | | ] |
| | | }, |
| | | { |
| | | id: 2, |
| | | quotationNo: 'QT202312002', |
| | | customer: 'æ·±å³çµåæéå
¬å¸', |
| | | salesperson: 'åé
å©·', |
| | | quotationDate: '2023-12-02', |
| | | validDate: '2023-12-31', |
| | | totalAmount: 35000.00, |
| | | paymentMethod: 'åæä»æ¬¾', |
| | | deliveryPeriod: '20天', |
| | | status: '客æ·ç¡®è®¤', |
| | | remark: 'å¸¸è§æ¥ä»·', |
| | | products: [ |
| | | { productName: 'æ§å¶æ¨¡å', specification: 'CTL-002', quantity: 5, unit: '个', unitPrice: 7000, amount: 35000 } |
| | | ] |
| | | }, |
| | | { |
| | | id: 3, |
| | | quotationNo: 'QT202312003', |
| | | customer: 'å京贸æå
¬å¸', |
| | | salesperson: 'ç建å½', |
| | | quotationDate: '2023-12-03', |
| | | validDate: '2023-12-31', |
| | | totalAmount: 28000.00, |
| | | paymentMethod: 'æç»', |
| | | deliveryPeriod: '15天', |
| | | status: 'è稿', |
| | | remark: 'æ°å®¢æ·æ¥ä»·', |
| | | products: [ |
| | | { productName: 'æ°æ®ééå¨', specification: 'DAQ-003', quantity: 4, unit: '个', unitPrice: 7000, amount: 28000 } |
| | | ] |
| | | } |
| | | ]) |
| | | |
| | | const pagination = reactive({ |
| | | total: 3, |
| | | currentPage: 1, |
| | | pageSize: 10 |
| | | }) |
| | | |
| | | const dialogVisible = ref(false) |
| | | const viewDialogVisible = ref(false) |
| | | const dialogTitle = ref('æ°å¢æ¥ä»·') |
| | | const form = reactive({ |
| | | customer: '', |
| | | salesperson: '', |
| | | quotationDate: '', |
| | | validDate: '', |
| | | paymentMethod: '', |
| | | deliveryPeriod: '', |
| | | status: 'è稿', |
| | | remark: '', |
| | | products: [], |
| | | subtotal: 0, |
| | | freight: 0, |
| | | otherFee: 0, |
| | | discountRate: 0, |
| | | discountAmount: 0, |
| | | totalAmount: 0 |
| | | }) |
| | | |
| | | const rules = { |
| | | customer: [{ required: true, message: 'è¯·éæ©å®¢æ·', trigger: 'change' }], |
| | | salesperson: [{ required: true, message: 'è¯·éæ©ä¸å¡å', trigger: 'change' }], |
| | | quotationDate: [{ required: true, message: 'è¯·éæ©æ¥ä»·æ¥æ', trigger: 'change' }], |
| | | validDate: [{ required: true, message: 'è¯·éæ©æææ', trigger: 'change' }], |
| | | paymentMethod: [{ required: true, message: 'è¯·éæ©ä»æ¬¾æ¹å¼', trigger: 'change' }], |
| | | deliveryPeriod: [{ required: true, message: '请è¾å
¥äº¤è´§æ', trigger: 'blur' }] |
| | | } |
| | | |
| | | const isEdit = ref(false) |
| | | const editId = ref(null) |
| | | const currentQuotation = ref({}) |
| | | const formRef = ref() |
| | | |
| | | // 计ç®å±æ§ |
| | | const filteredList = computed(() => { |
| | | let list = quotationList.value |
| | | if (searchForm.quotationNo) { |
| | | list = list.filter(item => item.quotationNo.includes(searchForm.quotationNo)) |
| | | } |
| | | if (searchForm.customer) { |
| | | list = list.filter(item => item.customer === searchForm.customer) |
| | | } |
| | | if (searchForm.status) { |
| | | list = list.filter(item => item.status === searchForm.status) |
| | | } |
| | | return list |
| | | }) |
| | | |
| | | // æ¹æ³ |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | 'è稿': 'info', |
| | | 'å·²åé': 'primary', |
| | | '客æ·ç¡®è®¤': 'success', |
| | | 'å·²è¿æ': 'danger' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | // æç´¢é»è¾å·²å¨computedä¸å¤ç |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.quotationNo = '' |
| | | searchForm.customer = '' |
| | | searchForm.status = '' |
| | | } |
| | | |
| | | const handleAdd = () => { |
| | | dialogTitle.value = 'æ°å¢æ¥ä»·' |
| | | isEdit.value = false |
| | | resetForm() |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleView = (row) => { |
| | | currentQuotation.value = row |
| | | viewDialogVisible.value = true |
| | | } |
| | | |
| | | const handleEdit = (row) => { |
| | | dialogTitle.value = 'ç¼è¾æ¥ä»·' |
| | | isEdit.value = true |
| | | editId.value = row.id |
| | | Object.assign(form, row) |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('确认å é¤è¯¥æ¥ä»·ååï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = quotationList.value.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | quotationList.value.splice(index, 1) |
| | | pagination.total-- |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const resetForm = () => { |
| | | form.customer = '' |
| | | form.salesperson = '' |
| | | form.quotationDate = '' |
| | | form.validDate = '' |
| | | form.paymentMethod = '' |
| | | form.deliveryPeriod = '' |
| | | form.status = 'è稿' |
| | | form.remark = '' |
| | | form.products = [] |
| | | form.subtotal = 0 |
| | | form.freight = 0 |
| | | form.otherFee = 0 |
| | | form.discountRate = 0 |
| | | form.discountAmount = 0 |
| | | form.totalAmount = 0 |
| | | } |
| | | |
| | | const addProduct = () => { |
| | | form.products.push({ |
| | | productName: '', |
| | | specification: '', |
| | | quantity: 1, |
| | | unit: '', |
| | | unitPrice: 0, |
| | | amount: 0 |
| | | }) |
| | | } |
| | | |
| | | const removeProduct = (index) => { |
| | | form.products.splice(index, 1) |
| | | calculateSubtotal() |
| | | } |
| | | |
| | | const calculateAmount = (product) => { |
| | | product.amount = product.quantity * product.unitPrice |
| | | calculateSubtotal() |
| | | } |
| | | |
| | | const calculateSubtotal = () => { |
| | | form.subtotal = form.products.reduce((sum, product) => sum + product.amount, 0) |
| | | calculateTotal() |
| | | } |
| | | |
| | | const calculateTotal = () => { |
| | | form.discountAmount = form.subtotal * (form.discountRate / 100) |
| | | form.totalAmount = form.subtotal + form.freight + form.otherFee - form.discountAmount |
| | | } |
| | | |
| | | const handleCustomerChange = () => { |
| | | // å¯ä»¥æ ¹æ®å®¢æ·ä¿¡æ¯èªå¨å¡«å
ä¸äºé»è®¤å¼ |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | if (form.products.length === 0) { |
| | | ElMessage.warning('请è³å°æ·»å ä¸ä¸ªäº§å') |
| | | return |
| | | } |
| | | |
| | | if (isEdit.value) { |
| | | // ç¼è¾ |
| | | const index = quotationList.value.findIndex(item => item.id === editId.value) |
| | | if (index > -1) { |
| | | quotationList.value[index] = { ...form, id: editId.value } |
| | | ElMessage.success('ç¼è¾æå') |
| | | } |
| | | } else { |
| | | // æ°å¢ |
| | | const newId = Math.max(...quotationList.value.map(item => item.id)) + 1 |
| | | const quotationNo = `QT${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(new Date().getDate()).padStart(2, '0')}${String(newId).padStart(3, '0')}` |
| | | quotationList.value.push({ |
| | | ...form, |
| | | id: newId, |
| | | quotationNo: quotationNo |
| | | }) |
| | | pagination.total++ |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleCurrentChange = (val) => { |
| | | pagination.currentPage = val.page |
| | | pagination.pageSize = val.limit |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .form-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .card-title { |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | </style> |