From bc278f02a34cdce5be02e42b26fe9e1bc6a0d6e6 Mon Sep 17 00:00:00 2001
From: maven <2163098428@qq.com>
Date: 星期四, 18 九月 2025 13:39:39 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev' into dev
---
src/views/collaborativeApproval/notificationManagement/summary/index.vue | 403 ++++
multiple/config.json | 10
src/api/publicApi/index.js | 42
src/views/collaborativeApproval/notificationManagement/meetSetting/index.vue | 306 +++
src/views/procurementManagement/index.vue | 42
src/views/salesManagement/salesLedger/index.vue | 646 ++++++
multiple/assets/favicon/CMNYico.ico | 0
src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue | 398 +++
src/api/inspectionManagement/index.js | 61
src/views/inventoryManagement/dispatchLog/index.vue | 1047 +++++-----
multiple/assets/logo/CMNYLogo.png | 0
src/api/salesManagement/salesQuotation.js | 112 +
src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue | 418 ++++
src/views/procurementManagement/procurementReport/index.vue | 31
src/views/collaborativeApproval/meetingBoard/index.vue | 228 -
src/api/inspectionUpload/index.js | 43
src/views/salesManagement/salesQuotation/index.vue | 604 ++++++
src/api/collaborativeApproval/meeting.js | 118 +
src/router/index.js | 198 -
src/views/collaborativeApproval/notificationManagement/meetIndex/index.vue | 371 +++
src/assets/images/video.png | 0
src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue | 416 ++++
package.json | 2
src/views/collaborativeApproval/notificationManagement/meetDraft/index.vue | 495 ++++
src/views/inspectionManagement/index.vue | 6
25 files changed, 5,164 insertions(+), 833 deletions(-)
diff --git a/multiple/assets/favicon/CMNYico.ico b/multiple/assets/favicon/CMNYico.ico
new file mode 100644
index 0000000..4536de7
--- /dev/null
+++ b/multiple/assets/favicon/CMNYico.ico
Binary files differ
diff --git a/multiple/assets/logo/CMNYLogo.png b/multiple/assets/logo/CMNYLogo.png
new file mode 100644
index 0000000..202664a
--- /dev/null
+++ b/multiple/assets/logo/CMNYLogo.png
Binary files differ
diff --git a/multiple/config.json b/multiple/config.json
index 738b52e..a1f5164 100644
--- a/multiple/config.json
+++ b/multiple/config.json
@@ -167,6 +167,16 @@
"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"
diff --git a/package.json b/package.json
index b57034d..e80e1e7 100644
--- a/package.json
+++ b/package.json
@@ -34,11 +34,13 @@
"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"
diff --git a/src/api/collaborativeApproval/meeting.js b/src/api/collaborativeApproval/meeting.js
new file mode 100644
index 0000000..20df8b1
--- /dev/null
+++ b/src/api/collaborativeApproval/meeting.js
@@ -0,0 +1,118 @@
+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",
+ });
+}
diff --git a/src/api/inspectionManagement/index.js b/src/api/inspectionManagement/index.js
new file mode 100644
index 0000000..d0c444a
--- /dev/null
+++ b/src/api/inspectionManagement/index.js
@@ -0,0 +1,61 @@
+// 宸℃绠$悊
+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
+ })
+}
\ No newline at end of file
diff --git a/src/api/inspectionUpload/index.js b/src/api/inspectionUpload/index.js
new file mode 100644
index 0000000..0d954e2
--- /dev/null
+++ b/src/api/inspectionUpload/index.js
@@ -0,0 +1,43 @@
+// 宸℃涓婁紶
+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
+ })
+}
\ No newline at end of file
diff --git a/src/api/publicApi/index.js b/src/api/publicApi/index.js
new file mode 100644
index 0000000..8022592
--- /dev/null
+++ b/src/api/publicApi/index.js
@@ -0,0 +1,42 @@
+// 鏂囨。绠$悊
+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
+ })
+}
\ No newline at end of file
diff --git a/src/api/salesManagement/salesQuotation.js b/src/api/salesManagement/salesQuotation.js
new file mode 100644
index 0000000..aae54cb
--- /dev/null
+++ b/src/api/salesManagement/salesQuotation.js
@@ -0,0 +1,112 @@
+// 閿�鍞姤浠烽〉闈㈡帴鍙�
+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",
+ });
+}
diff --git a/src/assets/images/video.png b/src/assets/images/video.png
new file mode 100644
index 0000000..7a90175
--- /dev/null
+++ b/src/assets/images/video.png
Binary files differ
diff --git a/src/router/index.js b/src/router/index.js
index 437e523..9c5a968 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -1,6 +1,6 @@
-import { createWebHistory, createRouter } from 'vue-router'
+import { createWebHistory, createRouter } from "vue-router";
/* Layout */
-import Layout from '@/layout'
+import Layout from "@/layout";
/**
* Note: 璺敱閰嶇疆椤�
@@ -16,85 +16,80 @@
* roles: ['admin', 'common'] // 璁块棶璺敱鐨勮鑹叉潈闄�
* permissions: ['a:a:a', 'b:b:b'] // 璁块棶璺敱鐨勮彍鍗曟潈闄�
* meta : {
- noCache: true // 濡傛灉璁剧疆涓簍rue锛屽垯涓嶄細琚� <keep-alive> 缂撳瓨(榛樿 false)
- title: 'title' // 璁剧疆璇ヨ矾鐢卞湪渚ц竟鏍忓拰闈㈠寘灞戜腑灞曠ず鐨勫悕瀛�
- icon: 'svg-name' // 璁剧疆璇ヨ矾鐢辩殑鍥炬爣锛屽搴旇矾寰剆rc/assets/icons/svg
- breadcrumb: false // 濡傛灉璁剧疆涓篺alse锛屽垯涓嶄細鍦╞readcrumb闈㈠寘灞戜腑鏄剧ず
- activeMenu: '/system/user' // 褰撹矾鐢辫缃簡璇ュ睘鎬э紝鍒欎細楂樹寒鐩稿搴旂殑渚ц竟鏍忋��
- }
+ noCache: true // 濡傛灉璁剧疆涓簍rue锛屽垯涓嶄細琚� <keep-alive> 缂撳瓨(榛樿 false)
+ title: 'title' // 璁剧疆璇ヨ矾鐢卞湪渚ц竟鏍忓拰闈㈠寘灞戜腑灞曠ず鐨勫悕瀛�
+ icon: 'svg-name' // 璁剧疆璇ヨ矾鐢辩殑鍥炬爣锛屽搴旇矾寰剆rc/assets/icons/svg
+ breadcrumb: false // 濡傛灉璁剧疆涓篺alse锛屽垯涓嶄細鍦╞readcrumb闈㈠寘灞戜腑鏄剧ず
+ 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",
@@ -111,98 +106,91 @@
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;
diff --git a/src/views/collaborativeApproval/meetingBoard/index.vue b/src/views/collaborativeApproval/meetingBoard/index.vue
index b6b803b..dfbb922 100644
--- a/src/views/collaborativeApproval/meetingBoard/index.vue
+++ b/src/views/collaborativeApproval/meetingBoard/index.vue
@@ -16,7 +16,7 @@
</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>
@@ -28,7 +28,7 @@
</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>
@@ -45,11 +45,11 @@
</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>
@@ -66,79 +66,18 @@
</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>
@@ -146,63 +85,21 @@
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: '浼氳瀹',
- 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: ['闄堝織寮�', '鍒橀泤濠�', '瀛欐槑鍗�', '瀹㈡埛浠h〃'],
- 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' }
- ]
- }
+
])
// 瀵硅瘽妗嗙浉鍏�
@@ -218,9 +115,9 @@
// 鑾峰彇鐘舵�佺被鍨�
const getStatusType = (status) => {
const statusMap = {
- 'ongoing': 'success',
- 'upcoming': 'warning',
- 'completed': 'info'
+ '2': 'success',
+ '1': 'warning',
+ '0': 'info'
}
return statusMap[status] || 'info'
}
@@ -228,9 +125,9 @@
// 鑾峰彇鐘舵�佹枃鏈�
const getStatusText = (status) => {
const statusMap = {
- 'ongoing': '杩涜涓�',
- 'upcoming': '鍗冲皢寮�濮�',
- 'completed': '宸插畬鎴�'
+ '2': '杩涜涓�',
+ '1': '鍗冲皢寮�濮�',
+ '0': '宸插畬鎴�'
}
return statusMap[status] || '鏈煡'
}
@@ -261,65 +158,16 @@
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>
@@ -480,19 +328,19 @@
.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>
\ No newline at end of file
+</style>
diff --git a/src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue b/src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue
new file mode 100644
index 0000000..1a2859a
--- /dev/null
+++ b/src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue
@@ -0,0 +1,398 @@
+<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>
diff --git a/src/views/collaborativeApproval/notificationManagement/meetDraft/index.vue b/src/views/collaborativeApproval/notificationManagement/meetDraft/index.vue
new file mode 100644
index 0000000..0c62b8b
--- /dev/null
+++ b/src/views/collaborativeApproval/notificationManagement/meetDraft/index.vue
@@ -0,0 +1,495 @@
+<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>
diff --git a/src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue b/src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue
new file mode 100644
index 0000000..0883ec3
--- /dev/null
+++ b/src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue
@@ -0,0 +1,418 @@
+<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>
diff --git a/src/views/collaborativeApproval/notificationManagement/meetIndex/index.vue b/src/views/collaborativeApproval/notificationManagement/meetIndex/index.vue
new file mode 100644
index 0000000..2e9d9a9
--- /dev/null
+++ b/src/views/collaborativeApproval/notificationManagement/meetIndex/index.vue
@@ -0,0 +1,371 @@
+<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>
diff --git a/src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue b/src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue
new file mode 100644
index 0000000..3f0fc69
--- /dev/null
+++ b/src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue
@@ -0,0 +1,416 @@
+<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>
diff --git a/src/views/collaborativeApproval/notificationManagement/meetSetting/index.vue b/src/views/collaborativeApproval/notificationManagement/meetSetting/index.vue
new file mode 100644
index 0000000..c290be4
--- /dev/null
+++ b/src/views/collaborativeApproval/notificationManagement/meetSetting/index.vue
@@ -0,0 +1,306 @@
+<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>
diff --git a/src/views/collaborativeApproval/notificationManagement/summary/index.vue b/src/views/collaborativeApproval/notificationManagement/summary/index.vue
new file mode 100644
index 0000000..04eaa4a
--- /dev/null
+++ b/src/views/collaborativeApproval/notificationManagement/summary/index.vue
@@ -0,0 +1,403 @@
+<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>
diff --git a/src/views/inspectionManagement/index.vue b/src/views/inspectionManagement/index.vue
index 7e5edc0..e95b51a 100644
--- a/src/views/inspectionManagement/index.vue
+++ b/src/views/inspectionManagement/index.vue
@@ -44,7 +44,7 @@
</div>
<div>
<div>
- <ETable :loading="tableLoading"
+ <PIMTable :loading="tableLoading"
:table-data="tableData"
:columns="tableColumns"
@selection-change="handleSelectionChange"
@@ -75,7 +75,7 @@
<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">
@@ -120,7 +120,7 @@
// 缁勪欢寮曞叆
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";
diff --git a/src/views/inventoryManagement/dispatchLog/index.vue b/src/views/inventoryManagement/dispatchLog/index.vue
index 177868d..65c90e8 100644
--- a/src/views/inventoryManagement/dispatchLog/index.vue
+++ b/src/views/inventoryManagement/dispatchLog/index.vue
@@ -1,248 +1,249 @@
<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>
@@ -251,8 +252,8 @@
import { ElMessageBox } from "element-plus";
import useUserStore from "@/store/modules/user";
import {
- getStockOutPage,
- delStockOut,
+ getStockOutPage,
+ delStockOut,
} from "@/api/inventoryManagement/stockOut.js";
const userStore = useUserStore();
@@ -261,8 +262,8 @@
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
- current: 1,
- size: 100,
+ current: 1,
+ size: 100,
});
const total = ref(0);
@@ -272,136 +273,136 @@
// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
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>
@@ -535,10 +536,10 @@
</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">
@@ -616,7 +617,7 @@
<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>
@@ -627,227 +628,227 @@
</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>
diff --git a/src/views/procurementManagement/index.vue b/src/views/procurementManagement/index.vue
index dd7c3c4..bf54384 100644
--- a/src/views/procurementManagement/index.vue
+++ b/src/views/procurementManagement/index.vue
@@ -101,6 +101,26 @@
</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">
@@ -112,6 +132,24 @@
<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>
@@ -179,7 +217,7 @@
<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()
@@ -195,6 +233,8 @@
approvedReturns: 3,
activePrices: 45,
pendingPrices: 2,
+ activePlans: 8,
+ pendingCalculations: 3,
totalOrders: 30,
totalAmount: 125.8,
avgDeliveryTime: 7,
diff --git a/src/views/procurementManagement/procurementReport/index.vue b/src/views/procurementManagement/procurementReport/index.vue
index a12b4ba..33a0e91 100644
--- a/src/views/procurementManagement/procurementReport/index.vue
+++ b/src/views/procurementManagement/procurementReport/index.vue
@@ -112,9 +112,9 @@
</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" />
@@ -160,11 +160,11 @@
</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" />
@@ -204,11 +204,11 @@
</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>
@@ -217,7 +217,7 @@
<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>
@@ -242,9 +242,9 @@
</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" />
@@ -805,6 +805,15 @@
: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) {
diff --git a/src/views/salesManagement/salesLedger/index.vue b/src/views/salesManagement/salesLedger/index.vue
index afe56b5..7482d2e 100644
--- a/src/views/salesManagement/salesLedger/index.vue
+++ b/src/views/salesManagement/salesLedger/index.vue
@@ -36,6 +36,7 @@
</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"
@@ -292,6 +293,120 @@
</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>
@@ -426,6 +541,9 @@
// 璁剧疆涓婁紶鐨勮姹傚ご閮�
headers: { Authorization: "Bearer " + getToken() },
});
+// 鎵撳嵃鐩稿叧
+const printPreviewVisible = ref(false);
+const printData = ref([]);
const changeDaterange = (value) => {
if (value) {
@@ -790,6 +908,333 @@
proxy.$modal.msg("宸插彇娑�");
});
};
+
+// 鎵撳嵃鍔熻兘
+const handlePrint = async () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佹墦鍗扮殑鏁版嵁");
+ return;
+ }
+
+ // 鏄剧ず鍔犺浇鐘舵��
+ proxy.$modal.loading("姝e湪鑾峰彇浜у搧鏁版嵁锛岃绋嶅��...");
+
+ 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();
@@ -798,6 +1243,41 @@
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);
@@ -1001,4 +1481,170 @@
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>
diff --git a/src/views/salesManagement/salesQuotation/index.vue b/src/views/salesManagement/salesQuotation/index.vue
new file mode 100644
index 0000000..b89dad1
--- /dev/null
+++ b/src/views/salesManagement/salesQuotation/index.vue
@@ -0,0 +1,604 @@
+<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>
--
Gitblit v1.9.3