From a4e179fe8c58d8824ef1301af28e442a3fa9102a Mon Sep 17 00:00:00 2001 From: spring <2396852758@qq.com> Date: 星期四, 14 八月 2025 14:18:33 +0800 Subject: [PATCH] Merge remote-tracking branch 'origin/dev_7004' into dev_7004 --- src/views/collaborativeApproval/meetingBoard/index.vue | 498 +++++++++++++ src/api/collaborativeApproval/noticeManagement.js | 69 + src/views/collaborativeApproval/warningSystem/index.vue | 307 ++++++++ src/views/demo/fakePage/index.vue | 248 ++++++ src/router/index.js | 392 +++++----- src/views/collaborativeApproval/noticeManagement/index.vue | 705 +++++++++++++++++++ 6 files changed, 2,017 insertions(+), 202 deletions(-) diff --git a/src/api/collaborativeApproval/noticeManagement.js b/src/api/collaborativeApproval/noticeManagement.js new file mode 100644 index 0000000..fa1caec --- /dev/null +++ b/src/api/collaborativeApproval/noticeManagement.js @@ -0,0 +1,69 @@ +import request from '@/utils/request' + +// 鏌ヨ鍏憡鍒楄〃 +export function listNotice(query) { + return request({ + url: '/collaborativeApproval/notice/list', + method: 'get', + params: query + }) +} + +// 鏌ヨ鍏憡璇︾粏 +export function getNotice(noticeId) { + return request({ + url: '/collaborativeApproval/notice/' + noticeId, + method: 'get' + }) +} + +// 鏂板鍏憡 +export function addNotice(data) { + return request({ + url: '/collaborativeApproval/notice', + method: 'post', + data: data + }) +} + +// 淇敼鍏憡 +export function updateNotice(data) { + return request({ + url: '/collaborativeApproval/notice', + method: 'put', + data: data + }) +} + +// 鍒犻櫎鍏憡 +export function delNotice(noticeId) { + return request({ + url: '/collaborativeApproval/notice/' + noticeId, + method: 'delete' + }) +} + +// 鎵归噺鍒犻櫎鍏憡 +export function delNoticeBatch(noticeIds) { + return request({ + url: '/collaborativeApproval/notice/batch', + method: 'delete', + data: noticeIds + }) +} + +// 鍙戝竷鍏憡 +export function publishNotice(noticeId) { + return request({ + url: '/collaborativeApproval/notice/publish/' + noticeId, + method: 'put' + }) +} + +// 涓嬬嚎鍏憡 +export function offlineNotice(noticeId) { + return request({ + url: '/collaborativeApproval/notice/offline/' + noticeId, + method: 'put' + }) +} diff --git a/src/router/index.js b/src/router/index.js index 2ee88bd..f6f0299 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,202 +1,190 @@ -import { createWebHistory, createRouter } from 'vue-router' -/* Layout */ -import Layout from '@/layout' - -/** - * Note: 璺敱閰嶇疆椤� - * - * hidden: true // 褰撹缃� true 鐨勬椂鍊欒璺敱涓嶄細鍐嶄晶杈规爮鍑虹幇 濡�401锛宭ogin绛夐〉闈紝鎴栬�呭涓�浜涚紪杈戦〉闈�/edit/1 - * alwaysShow: true // 褰撲綘涓�涓矾鐢变笅闈㈢殑 children 澹版槑鐨勮矾鐢卞ぇ浜�1涓椂锛岃嚜鍔ㄤ細鍙樻垚宓屽鐨勬ā寮�--濡傜粍浠堕〉闈� - * // 鍙湁涓�涓椂锛屼細灏嗛偅涓瓙璺敱褰撳仛鏍硅矾鐢辨樉绀哄湪渚ц竟鏍�--濡傚紩瀵奸〉闈� - * // 鑻ヤ綘鎯充笉绠¤矾鐢变笅闈㈢殑 children 澹版槑鐨勪釜鏁伴兘鏄剧ず浣犵殑鏍硅矾鐢� - * // 浣犲彲浠ヨ缃� alwaysShow: true锛岃繖鏍峰畠灏变細蹇界暐涔嬪墠瀹氫箟鐨勮鍒欙紝涓�鐩存樉绀烘牴璺敱 - * redirect: noRedirect // 褰撹缃� noRedirect 鐨勬椂鍊欒璺敱鍦ㄩ潰鍖呭睉瀵艰埅涓笉鍙鐐瑰嚮 - * name:'router-name' // 璁惧畾璺敱鐨勫悕瀛楋紝涓�瀹氳濉啓涓嶇劧浣跨敤<keep-alive>鏃朵細鍑虹幇鍚勭闂 - * query: '{"id": 1, "name": "ry"}' // 璁块棶璺敱鐨勯粯璁や紶閫掑弬鏁� - * 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' // 褰撹矾鐢辫缃簡璇ュ睘鎬э紝鍒欎細楂樹寒鐩稿搴旂殑渚ц竟鏍忋�� - } - */ - -// 鍏叡璺敱 -export const constantRoutes = [ - { - path: '/redirect', - component: Layout, - hidden: true, - children: [ - { - path: '/redirect/:path(.*)', - component: () => import('@/views/redirect/index.vue') - } - ] - }, - { - path: '/login', - component: () => import('@/views/login'), - hidden: true - }, - { - path: '/register', - component: () => import('@/views/register'), - hidden: true - }, - { - path: "/:pathMatch(.*)*", - component: () => import('@/views/error/404'), - hidden: true - }, - { - path: '/401', - component: () => import('@/views/error/401'), - hidden: true - }, - - { - path: '', - component: Layout, - redirect: '/index', - children: [ - { - path: '/index', - component: () => import('@/views/index'), - name: 'Index', - meta: { title: '棣栭〉', icon: 'dashboard', affix: true } - } - ] - }, - // { - // path: '/equipment', - // component: Layout, - // redirect: '/equipment/iot-monitor', - // children: [ - // { - // path: 'iot-monitor', - // component: () => import('@/views/equipmentManagement/iotMonitor/index.vue'), - // name: 'IoTMonitor', - // meta: { title: 'IoT鐩戞帶', icon: 'monitor', noCache: 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', - component: Layout, - hidden: true, - redirect: 'noredirect', - children: [ - { - path: 'profile', - component: () => import('@/views/system/user/profile/index'), - name: 'Profile', - meta: { title: '涓汉涓績', icon: 'user' } - } - ] - } -] - -// 鍔ㄦ�佽矾鐢憋紝鍩轰簬鐢ㄦ埛鏉冮檺鍔ㄦ�佸幓鍔犺浇 -export const dynamicRoutes = [ - { - path: '/system/user-auth', - component: Layout, - hidden: true, - permissions: ['system:user:edit'], - children: [ - { - path: 'role/:userId(\\d+)', - component: () => import('@/views/system/user/authRole'), - name: 'AuthRole', - meta: { title: '鍒嗛厤瑙掕壊', activeMenu: '/system/user' } - } - ] - }, - { - path: '/system/role-auth', - component: Layout, - hidden: true, - permissions: ['system:role:edit'], - children: [ - { - path: 'user/:roleId(\\d+)', - component: () => import('@/views/system/role/authUser'), - name: 'AuthUser', - meta: { title: '鍒嗛厤鐢ㄦ埛', activeMenu: '/system/role' } - } - ] - }, - { - path: '/system/dict-data', - component: Layout, - hidden: true, - permissions: ['system:dict:list'], - children: [ - { - path: 'index/:dictId(\\d+)', - component: () => import('@/views/system/dict/data'), - name: 'Data', - meta: { title: '瀛楀吀鏁版嵁', activeMenu: '/system/dict' } - } - ] - }, - { - path: '/monitor/job-log', - component: Layout, - hidden: true, - permissions: ['monitor:job:list'], - children: [ - { - path: 'index/:jobId(\\d+)', - component: () => import('@/views/monitor/job/log'), - name: 'JobLog', - meta: { title: '璋冨害鏃ュ織', activeMenu: '/monitor/job' } - } - ] - }, - { - path: '/tool/gen-edit', - component: Layout, - hidden: true, - permissions: ['tool:gen:edit'], - children: [ - { - 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 { top: 0 } - }, -}) - -export default router +import { createWebHistory, createRouter } from 'vue-router' +/* Layout */ +import Layout from '@/layout' + +/** + * Note: 璺敱閰嶇疆椤� + * + * hidden: true // 褰撹缃� true 鐨勬椂鍊欒璺敱涓嶄細鍐嶄晶杈规爮鍑虹幇 濡�401锛宭ogin绛夐〉闈紝鎴栬�呭涓�浜涚紪杈戦〉闈�/edit/1 + * alwaysShow: true // 褰撲綘涓�涓矾鐢变笅闈㈢殑 children 澹版槑鐨勮矾鐢卞ぇ浜�1涓椂锛岃嚜鍔ㄤ細鍙樻垚宓屽鐨勬ā寮�--濡傜粍浠堕〉闈� + * // 鍙湁涓�涓椂锛屼細灏嗛偅涓瓙璺敱褰撳仛鏍硅矾鐢辨樉绀哄湪渚ц竟鏍�--濡傚紩瀵奸〉闈� + * // 鑻ヤ綘鎯充笉绠¤矾鐢变笅闈㈢殑 children 澹版槑鐨勪釜鏁伴兘鏄剧ず浣犵殑鏍硅矾鐢� + * // 浣犲彲浠ヨ缃� alwaysShow: true锛岃繖鏍峰畠灏变細蹇界暐涔嬪墠瀹氫箟鐨勮鍒欙紝涓�鐩存樉绀烘牴璺敱 + * redirect: noRedirect // 褰撹缃� noRedirect 鐨勬椂鍊欒璺敱鍦ㄩ潰鍖呭睉瀵艰埅涓笉鍙鐐瑰嚮 + * name:'router-name' // 璁惧畾璺敱鐨勫悕瀛楋紝涓�瀹氳濉啓涓嶇劧浣跨敤<keep-alive>鏃朵細鍑虹幇鍚勭闂 + * query: '{"id": 1, "name": "ry"}' // 璁块棶璺敱鐨勯粯璁や紶閫掑弬鏁� + * 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' // 褰撹矾鐢辫缃簡璇ュ睘鎬э紝鍒欎細楂樹寒鐩稿搴旂殑渚ц竟鏍忋�� + } + */ + +// 鍏叡璺敱 +export const constantRoutes = [ + { + path: '/redirect', + component: Layout, + hidden: true, + children: [ + { + path: '/redirect/:path(.*)', + component: () => import('@/views/redirect/index.vue') + } + ] + }, + { + path: '/login', + component: () => import('@/views/login'), + hidden: true + }, + { + path: '/register', + component: () => import('@/views/register'), + hidden: true + }, + { + path: "/:pathMatch(.*)*", + component: () => import('@/views/error/404'), + hidden: true + }, + { + path: '/401', + component: () => import('@/views/error/401'), + hidden: true + }, + + { + path: '', + component: Layout, + redirect: '/index', + children: [ + { + path: '/index', + component: () => import('@/views/index'), + name: 'Index', + meta: { title: '棣栭〉', icon: 'dashboard', affix: true } + } + ] + }, + { + path: '/user', + component: Layout, + hidden: true, + redirect: 'noredirect', + children: [ + { + path: 'profile', + component: () => import('@/views/system/user/profile/index'), + name: 'Profile', + meta: { title: '涓汉涓績', icon: 'user' } + } + ] + } +] + +// 鍔ㄦ�佽矾鐢憋紝鍩轰簬鐢ㄦ埛鏉冮檺鍔ㄦ�佸幓鍔犺浇 +export const dynamicRoutes = [ + { + path: '/system/user-auth', + component: Layout, + hidden: true, + permissions: ['system:user:edit'], + children: [ + { + path: 'role/:userId(\\d+)', + component: () => import('@/views/system/user/authRole'), + name: 'AuthRole', + meta: { title: '鍒嗛厤瑙掕壊', activeMenu: '/system/user' } + } + ] + }, + { + path: '/main/MobileChat', + component: Layout, + redirect: '', + hidden: true, + permissions: ['MobileChat:edit'], + children: [ + { + path: '', + component: () => import('@/views/chatHome/chatHomeIndex/MobileChat'), + name: 'MobileChat', + meta: { title: 'AI瀵硅瘽', activeMenu: '/chatHome/chatHomeIndex'} + } + ] + }, + { + path: '/system/role-auth', + component: Layout, + hidden: true, + permissions: ['system:role:edit'], + children: [ + { + path: 'user/:roleId(\\d+)', + component: () => import('@/views/system/role/authUser'), + name: 'AuthUser', + meta: { title: '鍒嗛厤鐢ㄦ埛', activeMenu: '/system/role' } + } + ] + }, + { + path: '/system/dict-data', + component: Layout, + hidden: true, + permissions: ['system:dict:list'], + children: [ + { + path: 'index/:dictId(\\d+)', + component: () => import('@/views/system/dict/data'), + name: 'Data', + meta: { title: '瀛楀吀鏁版嵁', activeMenu: '/system/dict' } + } + ] + }, + { + path: '/monitor/job-log', + component: Layout, + hidden: true, + permissions: ['monitor:job:list'], + children: [ + { + path: 'index/:jobId(\\d+)', + component: () => import('@/views/monitor/job/log'), + name: 'JobLog', + meta: { title: '璋冨害鏃ュ織', activeMenu: '/monitor/job' } + } + ] + }, + { + path: '/tool/gen-edit', + component: Layout, + hidden: true, + permissions: ['tool:gen:edit'], + children: [ + { + 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 { top: 0 } + }, +}) + +export default router diff --git a/src/views/collaborativeApproval/meetingBoard/index.vue b/src/views/collaborativeApproval/meetingBoard/index.vue new file mode 100644 index 0000000..63c74f9 --- /dev/null +++ b/src/views/collaborativeApproval/meetingBoard/index.vue @@ -0,0 +1,498 @@ +<template> + <div class="app-container"> + <!-- 椤甸潰鏍囬 --> + <div class="page-header"> + <h2>浼氳鐪嬫澘</h2> +<!-- <el-button type="primary" @click="createMeeting">鍒涘缓浼氳</el-button>--> + </div> + + <!-- 浼氳缁熻鍗$墖 --> + <div class="stats-cards"> + <el-card class="stat-card"> + <div class="stat-content"> + <div class="stat-number">{{ stats.total }}</div> + <div class="stat-label">鎬讳細璁暟</div> + </div> + </el-card> + <el-card class="stat-card"> + <div class="stat-content"> + <div class="stat-number">{{ stats.ongoing }}</div> + <div class="stat-label">杩涜涓�</div> + </div> + </el-card> + <el-card class="stat-card"> + <div class="stat-content"> + <div class="stat-number">{{ stats.completed }}</div> + <div class="stat-label">宸插畬鎴�</div> + </div> + </el-card> + <el-card class="stat-card"> + <div class="stat-content"> + <div class="stat-number">{{ stats.upcoming }}</div> + <div class="stat-label">鍗冲皢寮�濮�</div> + </div> + </el-card> + </div> + + <!-- 浼氳鍒楄〃 --> + <div class="meeting-list"> + <el-card v-for="meeting in meetings" :key="meeting.id" class="meeting-card"> + <div class="meeting-header"> + <div class="meeting-title"> + <h3>{{ meeting.title }}</h3> + <el-tag :type="getStatusType(meeting.status)" size="small"> + {{ getStatusText(meeting.status) }} + </el-tag> + </div> + <div class="meeting-time"> + <el-icon><Clock /></el-icon> + {{ formatTime(meeting.startTime) }} - {{ formatTime(meeting.endTime) }} + </div> + </div> + + <div class="meeting-info"> + <div class="info-item"> + <el-icon><Location /></el-icon> + <span>{{ meeting.location }}</span> + </div> + <div class="info-item"> + <el-icon><User /></el-icon> + <span>涓绘寔浜�: {{ meeting.host }}</span> + </div> + <div class="info-item"> + <el-icon><UserFilled /></el-icon> + <span>鍙備細浜烘暟: {{ meeting.participants.length }}浜�</span> + </div> + </div> + + <div class="meeting-agenda"> + <h4>璁▼瀹夋帓</h4> + <div class="agenda-list"> + <div + v-for="(agenda, index) in meeting.agenda" + :key="index" + class="agenda-item" + :class="{ 'active': agenda.status === 'active', 'completed': agenda.status === 'completed' }" + > + <span class="agenda-time">{{ agenda.time }}</span> + <span class="agenda-content">{{ agenda.content }}</span> + <el-tag + :type="getAgendaStatusType(agenda.status)" + size="small" + > + {{ getAgendaStatusText(agenda.status) }} + </el-tag> + </div> + </div> + </div> + +<!-- <div class="meeting-actions">--> +<!-- <el-button type="primary" size="small" @click="joinMeeting(meeting)">--> +<!-- 鍔犲叆浼氳--> +<!-- </el-button>--> +<!-- <el-button type="info" size="small" @click="viewDetails(meeting)">--> +<!-- 鏌ョ湅璇︽儏--> +<!-- </el-button>--> +<!-- <el-button type="warning" size="small" @click="editMeeting(meeting)">--> +<!-- 缂栬緫--> +<!-- </el-button>--> +<!-- </div>--> + </el-card> + </div> + + <!-- 鍒涘缓浼氳瀵硅瘽妗� --> + <el-dialog v-model="dialogVisible" title="鍒涘缓浼氳" width="600px"> + <el-form :model="meetingForm" label-width="100px"> + <el-form-item label="浼氳鏍囬"> + <el-input v-model="meetingForm.title" placeholder="璇疯緭鍏ヤ細璁爣棰�" /> + </el-form-item> + <el-form-item label="浼氳鏃堕棿"> + <el-date-picker + v-model="meetingForm.timeRange" + type="datetimerange" + range-separator="鑷�" + start-placeholder="寮�濮嬫椂闂�" + end-placeholder="缁撴潫鏃堕棿" + format="YYYY-MM-DD HH:mm" + value-format="YYYY-MM-DD HH:mm:ss" + /> + </el-form-item> + <el-form-item label="浼氳鍦扮偣"> + <el-input v-model="meetingForm.location" placeholder="璇疯緭鍏ヤ細璁湴鐐�" /> + </el-form-item> + <el-form-item label="涓绘寔浜�"> + <el-input v-model="meetingForm.host" placeholder="璇疯緭鍏ヤ富鎸佷汉濮撳悕" /> + </el-form-item> + <el-form-item label="浼氳鎻忚堪"> + <el-input + v-model="meetingForm.description" + type="textarea" + :rows="3" + placeholder="璇疯緭鍏ヤ細璁弿杩�" + /> + </el-form-item> + </el-form> + <template #footer> + <span class="dialog-footer"> + <el-button @click="dialogVisible = false">鍙栨秷</el-button> + <el-button type="primary" @click="submitMeeting">纭畾</el-button> + </span> + </template> + </el-dialog> + </div> +</template> + +<script setup> +import { ref, reactive, onMounted } from 'vue' +import { ElMessage } from 'element-plus' +import { Clock, Location, User, UserFilled } from '@element-plus/icons-vue' + +// 缁熻鏁版嵁 +const stats = reactive({ + total: 12, + ongoing: 3, + completed: 7, + upcoming: 2 +}) + +// 浼氳鏁版嵁 +const meetings = ref([ + { + id: 1, + title: '浜у搧寮�鍙戝懆浼�', + status: 'ongoing', + startTime: '2024-01-15 09:00:00', + endTime: '2024-01-15 10:30:00', + location: '浼氳瀹', + host: '寮犵粡鐞�', + participants: ['寮犵粡鐞�', '鏉庡伐绋嬪笀', '鐜嬭璁″笀', '璧垫祴璇曞憳'], + agenda: [ + { time: '09:00-09:15', content: '涓婂懆宸ヤ綔鎬荤粨', status: 'completed' }, + { time: '09:15-09:45', content: '鏈懆寮�鍙戣鍒�', status: 'active' }, + { time: '09:45-10:00', content: '鎶�鏈毦鐐硅璁�', status: 'pending' }, + { time: '10:00-10:30', content: '闂鍙嶉涓庤В鍐�', status: 'pending' } + ] + }, + { + id: 2, + title: '瀹㈡埛闇�姹傝瘎瀹′細', + status: 'upcoming', + startTime: '2024-01-15 14:00:00', + endTime: '2024-01-15 15:00:00', + location: '绾夸笂浼氳', + host: '闄堟�荤洃', + participants: ['闄堟�荤洃', '鍒樹骇鍝佺粡鐞�', '瀛欏鎴风粡鐞�', '瀹㈡埛浠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: '2024-01-14 16:00:00', + endTime: '2024-01-14 18:00:00', + location: '鍏徃澶у巺', + host: '浜轰簨閮�', + participants: ['鍏ㄤ綋鍛樺伐'], + agenda: [ + { time: '16:00-16:30', content: '鍥㈤槦娓告垙', status: 'completed' }, + { time: '16:30-17:00', content: '缁忛獙鍒嗕韩', status: 'completed' }, + { time: '17:00-18:00', content: '鑷敱浜ゆ祦', status: 'completed' } + ] + } +]) + +// 瀵硅瘽妗嗙浉鍏� +const dialogVisible = ref(false) +const meetingForm = reactive({ + title: '', + timeRange: [], + location: '', + host: '', + description: '' +}) + +// 鑾峰彇鐘舵�佺被鍨� +const getStatusType = (status) => { + const statusMap = { + 'ongoing': 'success', + 'upcoming': 'warning', + 'completed': 'info' + } + return statusMap[status] || 'info' +} + +// 鑾峰彇鐘舵�佹枃鏈� +const getStatusText = (status) => { + const statusMap = { + 'ongoing': '杩涜涓�', + 'upcoming': '鍗冲皢寮�濮�', + 'completed': '宸插畬鎴�' + } + return statusMap[status] || '鏈煡' +} + +// 鑾峰彇璁▼鐘舵�佺被鍨� +const getAgendaStatusType = (status) => { + const statusMap = { + 'completed': 'success', + 'active': 'warning', + 'pending': 'info' + } + return statusMap[status] || 'info' +} + +// 鑾峰彇璁▼鐘舵�佹枃鏈� +const getAgendaStatusText = (status) => { + const statusMap = { + 'completed': '宸插畬鎴�', + 'active': '杩涜涓�', + 'pending': '寰呭紑濮�' + } + return statusMap[status] || '鏈煡' +} + +// 鏍煎紡鍖栨椂闂� +const formatTime = (timeStr) => { + const date = new Date(timeStr) + return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) +} + +// 鍒涘缓浼氳 +const createMeeting = () => { + dialogVisible.value = true + // 閲嶇疆琛ㄥ崟 + Object.assign(meetingForm, { + title: '', + timeRange: [], + location: '', + host: '', + description: '' + }) +} + +// 鎻愪氦浼氳 +const submitMeeting = () => { + if (!meetingForm.title || !meetingForm.timeRange.length || !meetingForm.location || !meetingForm.host) { + ElMessage.warning('璇峰~鍐欏畬鏁寸殑浼氳淇℃伅') + return + } + + // 鍒涘缓鏂颁細璁� + const newMeeting = { + id: Date.now(), + title: meetingForm.title, + status: 'upcoming', + startTime: meetingForm.timeRange[0], + endTime: meetingForm.timeRange[1], + location: meetingForm.location, + host: meetingForm.host, + participants: [meetingForm.host], + agenda: [ + { time: '寰呭畾', content: '璁▼寰呭畾', status: 'pending' } + ] + } + + meetings.value.unshift(newMeeting) + stats.total++ + stats.upcoming++ + + ElMessage.success('浼氳鍒涘缓鎴愬姛') + dialogVisible.value = false +} + +// 鍔犲叆浼氳 +const joinMeeting = (meeting) => { + ElMessage.success(`宸插姞鍏ヤ細璁細${meeting.title}`) +} + +// 鏌ョ湅璇︽儏 +const viewDetails = (meeting) => { + ElMessage.info(`鏌ョ湅浼氳璇︽儏锛�${meeting.title}`) +} + +// 缂栬緫浼氳 +const editMeeting = (meeting) => { + ElMessage.info(`缂栬緫浼氳锛�${meeting.title}`) +} + +onMounted(() => { + console.log('浼氳鐪嬫澘椤甸潰鍔犺浇瀹屾垚') +}) +</script> + +<style scoped> +.app-container { + padding: 20px; +} + +.page-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.page-header h2 { + margin: 0; + color: #303133; +} + +.stats-cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +.stat-card { + text-align: center; +} + +.stat-content { + padding: 10px; +} + +.stat-number { + font-size: 32px; + font-weight: bold; + color: #409eff; + margin-bottom: 8px; +} + +.stat-label { + font-size: 14px; + color: #606266; +} + +.meeting-list { + display: grid; + gap: 20px; +} + +.meeting-card { + border-radius: 8px; +} + +.meeting-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 15px; +} + +.meeting-title { + display: flex; + align-items: center; + gap: 10px; +} + +.meeting-title h3 { + margin: 0; + color: #303133; +} + +.meeting-time { + display: flex; + align-items: center; + gap: 5px; + color: #606266; + font-size: 14px; +} + +.meeting-info { + display: flex; + gap: 20px; + margin-bottom: 20px; + flex-wrap: wrap; +} + +.info-item { + display: flex; + align-items: center; + gap: 5px; + color: #606266; + font-size: 14px; +} + +.meeting-agenda { + margin-bottom: 20px; +} + +.meeting-agenda h4 { + margin: 0 0 15px 0; + color: #303133; + font-size: 16px; +} + +.agenda-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.agenda-item { + display: flex; + align-items: center; + gap: 15px; + padding: 10px; + border-radius: 6px; + background-color: #f5f7fa; +} + +.agenda-item.active { + background-color: #fdf6ec; + border-left: 3px solid #e6a23c; +} + +.agenda-item.completed { + background-color: #f0f9ff; + border-left: 3px solid #409eff; +} + +.agenda-time { + font-weight: bold; + color: #606266; + min-width: 80px; +} + +.agenda-content { + flex: 1; + color: #303133; +} + +.meeting-actions { + display: flex; + gap: 10px; + justify-content: flex-end; +} + +.dialog-footer { + display: flex; + justify-content: flex-end; + gap: 10px; +} + +@media (max-width: 768px) { + .stats-cards { + grid-template-columns: repeat(2, 1fr); + } + + .meeting-header { + flex-direction: column; + gap: 10px; + } + + .meeting-info { + flex-direction: column; + gap: 10px; + } + + .meeting-actions { + flex-direction: column; + } +} +</style> \ No newline at end of file diff --git a/src/views/collaborativeApproval/noticeManagement/index.vue b/src/views/collaborativeApproval/noticeManagement/index.vue new file mode 100644 index 0000000..daa4cd7 --- /dev/null +++ b/src/views/collaborativeApproval/noticeManagement/index.vue @@ -0,0 +1,705 @@ +<template> + <div class="app-container"> + <!-- 鎼滅储琛ㄥ崟 --> + <div class="search_form"> + <div> + <span class="search_title">鍏憡鏍囬锛�</span> + <el-input + v-model="searchForm.noticeTitle" + style="width: 240px" + placeholder="璇疯緭鍏ュ叕鍛婃爣棰樻悳绱�" + @change="handleQuery" + clearable + :prefix-icon="Search" + /> + <span class="search_title ml10">鍏憡绫诲瀷锛�</span> + <el-select v-model="searchForm.noticeType" clearable @change="handleQuery" style="width: 240px"> + <el-option label="鏀惧亣閫氱煡" value="1" /> + <el-option label="璁惧缁翠慨閫氱煡" value="2" /> + </el-select> + <span class="search_title ml10">鐘舵�侊細</span> + <el-select v-model="searchForm.status" clearable @change="handleQuery" style="width: 240px"> + <el-option label="鑽夌" value="0" /> + <el-option label="宸插彂甯�" value="1" /> + <el-option label="宸蹭笅绾�" value="2" /> + </el-select> + <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button> + <el-button @click="resetQuery" style="margin-left: 10px">閲嶇疆</el-button> + </div> + <div> + <el-button type="primary" @click="openForm('add')">鏂板鍏憡</el-button> + <el-button type="danger" plain @click="handleDelete" :disabled="!selectedIds.length">鍒犻櫎</el-button> + </div> + </div> + + <!-- 閫氱煡鍏憡鏉� --> + <div class="notice-board"> + <!-- 鏀惧亣閫氱煡鍖哄煙 --> + <div class="notice-section" v-if="holidayNotices.length > 0"> + <div class="section-header"> + <h3>馃搮 鏀惧亣閫氱煡</h3> + <span class="section-count">{{ holidayNotices.length }}鏉�</span> + </div> + <div class="notice-cards"> + <div + v-for="notice in holidayNotices" + :key="notice.id" + class="notice-card holiday-card" + :class="{ 'urgent': notice.priority === '3' }" + > + <div class="card-header"> + <div class="card-title"> + <el-icon class="holiday-icon"><Calendar /></el-icon> + {{ notice.noticeTitle }} + </div> + <div class="card-actions"> + <el-button link type="primary" @click="handleEdit(notice)">缂栬緫</el-button> + <el-button link type="danger" @click="handleDelete(notice.id)">鍒犻櫎</el-button> + </div> + </div> + <div class="card-content"> + <p>{{ notice.noticeContent }}</p> + </div> + <div class="card-footer"> + <div class="card-meta"> + <span class="priority" :class="'priority-' + notice.priority"> + {{ getPriorityText(notice.priority) }} + </span> + <span class="status" :class="'status-' + notice.status"> + {{ getStatusText(notice.status) }} + </span> + </div> + <div class="card-info"> + <span class="creator">{{ notice.createBy }}</span> + <span class="time">{{ notice.createTime }}</span> + </div> + </div> + <div class="card-remark" v-if="notice.remark"> + <el-icon><InfoFilled /></el-icon> + <span>{{ notice.remark }}</span> + </div> + </div> + </div> + </div> + + <!-- 璁惧缁翠慨閫氱煡鍖哄煙 --> + <div class="notice-section" v-if="maintenanceNotices.length > 0"> + <div class="section-header"> + <h3>馃敡 璁惧缁翠慨閫氱煡</h3> + <span class="section-count">{{ maintenanceNotices.length }}鏉�</span> + </div> + <div class="notice-cards"> + <div + v-for="notice in maintenanceNotices" + :key="notice.id" + class="notice-card maintenance-card" + :class="{ 'urgent': notice.priority === '3' }" + > + <div class="card-header"> + <div class="card-title"> + <el-icon class="maintenance-icon"><Tools /></el-icon> + {{ notice.noticeTitle }} + </div> + <div class="card-actions"> + <el-button link type="primary" @click="handleEdit(notice)">缂栬緫</el-button> + <el-button link type="danger" @click="handleDelete(notice.id)">鍒犻櫎</el-button> + </div> + </div> + <div class="card-content"> + <p>{{ notice.noticeContent }}</p> + </div> + <div class="card-footer"> + <div class="card-meta"> + <span class="priority" :class="'priority-' + notice.priority"> + {{ getPriorityText(notice.priority) }} + </span> + <span class="status" :class="'status-' + notice.status"> + {{ getStatusText(notice.status) }} + </span> + </div> + <div class="card-info"> + <span class="creator">{{ notice.createBy }}</span> + <span class="time">{{ notice.createTime }}</span> + </div> + </div> + <div class="card-remark" v-if="notice.remark"> + <el-icon><InfoFilled /></el-icon> + <span>{{ notice.remark }}</span> + </div> + </div> + </div> + </div> + + <!-- 绌虹姸鎬� --> + <div class="empty-state" v-if="filteredNotices.length === 0"> + <el-empty description="鏆傛棤閫氱煡鍏憡" /> + </div> + </div> + + <!-- 鏂板/缂栬緫瀵硅瘽妗� --> + <el-dialog + :title="dialogTitle" + v-model="dialogVisible" + width="800px" + append-to-body + @close="resetForm" + > + <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> + <el-row> + <el-col :span="12"> + <el-form-item label="鍏憡鏍囬" prop="noticeTitle"> + <el-input v-model="form.noticeTitle" placeholder="璇疯緭鍏ュ叕鍛婃爣棰�" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="鍏憡绫诲瀷" prop="noticeType"> + <el-select v-model="form.noticeType" placeholder="璇烽�夋嫨鍏憡绫诲瀷" style="width: 100%"> + <el-option label="鏀惧亣閫氱煡" value="1" /> + <el-option label="璁惧缁翠慨閫氱煡" value="2" /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="鐘舵��"> + <el-radio-group v-model="form.status"> + <el-radio value="0">鑽夌</el-radio> + <el-radio value="1">宸插彂甯�</el-radio> + <el-radio value="2">宸蹭笅绾�</el-radio> + </el-radio-group> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="浼樺厛绾�"> + <el-select v-model="form.priority" placeholder="璇烽�夋嫨浼樺厛绾�" style="width: 100%"> + <el-option label="鏅��" value="1" /> + <el-option label="閲嶈" value="2" /> + <el-option label="绱ф��" value="3" /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="24"> + <el-form-item label="鍏憡鍐呭" prop="noticeContent"> + <el-input + v-model="form.noticeContent" + type="textarea" + :rows="6" + placeholder="璇疯緭鍏ュ叕鍛婂唴瀹�" + maxlength="500" + show-word-limit + /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="24"> + <el-form-item label="澶囨敞"> + <el-input + v-model="form.remark" + type="textarea" + :rows="3" + placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�" + maxlength="200" + show-word-limit + /> + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitForm">纭� 瀹�</el-button> + <el-button @click="dialogVisible = false">鍙� 娑�</el-button> + </div> + </template> + </el-dialog> + </div> +</template> + +<script setup> +import { Search, Calendar, Tools, InfoFilled } from "@element-plus/icons-vue"; +import { onMounted, ref, reactive, toRefs, computed } from "vue"; +import { ElMessage, ElMessageBox } from "element-plus"; +import useUserStore from "@/store/modules/user"; + +const userStore = useUserStore(); + +// 鍝嶅簲寮忔暟鎹� +const data = reactive({ + searchForm: { + noticeTitle: "", + noticeType: "", + status: "", + }, + form: { + id: undefined, + noticeTitle: "", + noticeType: "", + noticeContent: "", + status: "0", + priority: "1", + remark: "", + createBy: "", + createTime: "", + }, + rules: { + noticeTitle: [ + { required: true, message: "鍏憡鏍囬涓嶈兘涓虹┖", trigger: "blur" } + ], + noticeType: [ + { required: true, message: "璇烽�夋嫨鍏憡绫诲瀷", trigger: "change" } + ], + noticeContent: [ + { required: true, message: "鍏憡鍐呭涓嶈兘涓虹┖", trigger: "blur" } + ] + } +}); + +const { searchForm, form, rules } = toRefs(data); + +// 椤甸潰鐘舵�� +const dialogVisible = ref(false); +const dialogTitle = ref(""); +const selectedIds = ref([]); +const formRef = ref(); + +// 妯℃嫙鏁版嵁 - 鏍规嵁娉曞畾鑺傚亣鏃ヨ璁� +const mockData = [ + { + id: 1, + noticeTitle: "2024骞存槬鑺傛斁鍋囬�氱煡", + noticeType: "1", + priority: "2", + status: "1", + noticeContent: "鏍规嵁鍥藉姟闄㈠姙鍏巺閫氱煡锛�2024骞存槬鑺傛斁鍋囧畨鎺掑涓嬶細2鏈�10鏃ワ紙鍒濅竴锛夎嚦2鏈�17鏃ワ紙鍒濆叓锛夋斁鍋囪皟浼戯紝鍏�8澶┿��2鏈�4鏃ワ紙鏄熸湡鏃ワ級銆�2鏈�18鏃ワ紙鏄熸湡鏃ワ級涓婄彮銆傝鍚勯儴闂ㄦ彁鍓嶅仛濂藉伐浣滃畨鎺掋��", + remark: "鏀惧亣鏈熼棿璇蜂繚鎸佹墜鏈虹晠閫氾紝濡傛湁绱ф�ヤ簨鍔″強鏃惰仈绯�", + createBy: "浜轰簨閮�", + createTime: "2024-01-15 10:30:00" + }, + { + id: 2, + noticeTitle: "2024骞存竻鏄庤妭鏀惧亣閫氱煡", + noticeType: "1", + priority: "1", + status: "1", + noticeContent: "鏍规嵁鍥藉姟闄㈠姙鍏巺閫氱煡锛�2024骞存竻鏄庤妭鏀惧亣瀹夋帓濡備笅锛�4鏈�4鏃ワ紙鏄熸湡鍥涳級鑷�4鏈�6鏃ワ紙鏄熸湡鍏級鏀惧亣璋冧紤锛屽叡3澶┿��4鏈�7鏃ワ紙鏄熸湡鏃ワ級涓婄彮銆�", + remark: "璇峰悇閮ㄩ棬鍋氬ソ鍊肩彮瀹夋帓锛岀‘淇濊妭鏃ユ湡闂村悇椤瑰伐浣滄甯歌繍杞�", + createBy: "琛屾斂閮�", + createTime: "2024-01-14 14:20:00" + }, + { + id: 3, + noticeTitle: "2024骞村姵鍔ㄨ妭鏀惧亣閫氱煡", + noticeType: "1", + priority: "1", + status: "1", + noticeContent: "鏍规嵁鍥藉姟闄㈠姙鍏巺閫氱煡锛�2024骞村姵鍔ㄨ妭鏀惧亣瀹夋帓濡備笅锛�5鏈�1鏃ワ紙鏄熸湡涓夛級鑷�5鏈�5鏃ワ紙鏄熸湡鏃ワ級鏀惧亣璋冧紤锛屽叡5澶┿��4鏈�28鏃ワ紙鏄熸湡鏃ワ級銆�5鏈�11鏃ワ紙鏄熸湡鍏級涓婄彮銆�", + remark: "鏀惧亣鍓嶈鍏抽棴鐢垫簮锛岄攣濂介棬绐楋紝娉ㄦ剰瀹夊叏", + createBy: "琛屾斂閮�", + createTime: "2024-01-13 09:15:00" + }, + { + id: 4, + noticeTitle: "2024骞寸鍗堣妭鏀惧亣閫氱煡", + noticeType: "1", + priority: "1", + status: "1", + noticeContent: "鏍规嵁鍥藉姟闄㈠姙鍏巺閫氱煡锛�2024骞寸鍗堣妭鏀惧亣瀹夋帓濡備笅锛�6鏈�8鏃ワ紙鏄熸湡鍏級鑷�6鏈�10鏃ワ紙鏄熸湡涓�锛夋斁鍋囪皟浼戯紝鍏�3澶┿��6鏈�11鏃ワ紙鏄熸湡浜岋級涓婄彮銆�", + remark: "绁濆ぇ瀹剁鍗堣妭蹇箰锛岄槚瀹跺垢绂忥紒", + createBy: "琛屾斂閮�", + createTime: "2024-01-12 16:30:00" + }, + { + id: 5, + noticeTitle: "2024骞翠腑绉嬭妭鏀惧亣閫氱煡", + noticeType: "1", + priority: "1", + status: "1", + noticeContent: "鏍规嵁鍥藉姟闄㈠姙鍏巺閫氱煡锛�2024骞翠腑绉嬭妭鏀惧亣瀹夋帓濡備笅锛�9鏈�15鏃ワ紙鏄熸湡鏃ワ級鑷�9鏈�17鏃ワ紙鏄熸湡浜岋級鏀惧亣璋冧紤锛屽叡3澶┿��9鏈�14鏃ワ紙鏄熸湡鍏級涓婄彮銆�", + remark: "涓浣宠妭锛岀澶у鍥㈠渾缇庢弧锛屽垢绂忓畨搴凤紒", + createBy: "琛屾斂閮�", + createTime: "2024-01-11 11:20:00" + }, + { + id: 6, + noticeTitle: "2024骞村浗搴嗚妭鏀惧亣閫氱煡", + noticeType: "1", + priority: "2", + status: "1", + noticeContent: "鏍规嵁鍥藉姟闄㈠姙鍏巺閫氱煡锛�2024骞村浗搴嗚妭鏀惧亣瀹夋帓濡備笅锛�10鏈�1鏃ワ紙鏄熸湡浜岋級鑷�10鏈�7鏃ワ紙鏄熸湡涓�锛夋斁鍋囪皟浼戯紝鍏�7澶┿��9鏈�29鏃ワ紙鏄熸湡鏃ワ級銆�10鏈�12鏃ワ紙鏄熸湡鍏級涓婄彮銆�", + remark: "鍥藉簡鏈熼棿璇峰悇閮ㄩ棬鍋氬ソ鍊肩彮瀹夋帓锛岀‘淇濆畨鍏ㄧǔ瀹�", + createBy: "琛屾斂閮�", + createTime: "2024-01-10 15:45:00" + }, + { + id: 7, + noticeTitle: "A杞﹂棿鐢熶骇绾垮勾搴︽淇�氱煡", + noticeType: "2", + priority: "2", + status: "1", + noticeContent: "A杞﹂棿鐢熶骇绾垮皢浜�2024骞�1鏈�20鏃ワ紙鍛ㄥ叚锛夎繘琛屽勾搴︽淇淮鎶わ紝棰勮鍋滃伐8灏忔椂銆傛淇唴瀹瑰寘鎷細璁惧娓呮磥銆佹鼎婊戜繚鍏汇�佸畨鍏ㄨ缃鏌ョ瓑銆傝鐢熶骇閮ㄩ棬鎻愬墠璋冩暣鐢熶骇璁″垝銆�", + remark: "缁翠慨鏈熼棿璇风浉鍏充汉鍛橀厤鍚堬紝纭繚妫�淇伐浣滃畨鍏ㄩ『鍒╄繘琛�", + createBy: "璁惧閮�", + createTime: "2024-01-14 14:20:00" + }, + { + id: 8, + noticeTitle: "B杞﹂棿璁惧棰勯槻鎬х淮鎶ら�氱煡", + noticeType: "2", + priority: "1", + status: "1", + noticeContent: "B杞﹂棿鍏抽敭璁惧灏嗕簬2024骞�1鏈�25鏃ヨ繘琛岄闃叉�х淮鎶わ紝棰勮鍋滃伐4灏忔椂銆傜淮鎶ゅ唴瀹瑰寘鎷細璁惧妫�鏌ャ�侀浂浠舵洿鎹€�佹�ц兘娴嬭瘯绛夈�傝鐩稿叧閮ㄩ棬閰嶅悎銆�", + remark: "缁存姢瀹屾垚鍚庡皢杩涜璇曡繍琛岋紝纭繚璁惧姝e父杩愯", + createBy: "璁惧閮�", + createTime: "2024-01-13 09:15:00" + } +]; + +// 璁$畻灞炴�� +const filteredNotices = computed(() => { + let filtered = [...mockData]; + + if (searchForm.value.noticeTitle) { + filtered = filtered.filter(item => + item.noticeTitle.includes(searchForm.value.noticeTitle) + ); + } + if (searchForm.value.noticeType) { + filtered = filtered.filter(item => + item.noticeType === searchForm.value.noticeType + ); + } + if (searchForm.value.status !== "") { + filtered = filtered.filter(item => + item.status === searchForm.value.status + ); + } + + return filtered; +}); + +const holidayNotices = computed(() => { + return filteredNotices.value.filter(notice => notice.noticeType === "1"); +}); + +const maintenanceNotices = computed(() => { + return filteredNotices.value.filter(notice => notice.noticeType === "2"); +}); + +// 鏂规硶瀹氫箟 +const handleQuery = () => { + // 鎼滅储鍔熻兘淇濇寔涓嶅彉锛屼絾鏁版嵁閫氳繃璁$畻灞炴�ц嚜鍔ㄨ繃婊� +}; + +const resetQuery = () => { + searchForm.value = { + noticeTitle: "", + noticeType: "", + status: "" + }; +}; + +const getPriorityText = (priority) => { + const priorityMap = { "1": "鏅��", "2": "閲嶈", "3": "绱ф��" }; + return priorityMap[priority] || "鏅��"; +}; + +const getStatusText = (status) => { + const statusMap = { "0": "鑽夌", "1": "宸插彂甯�", "2": "宸蹭笅绾�" }; + return statusMap[status] || "鏈煡"; +}; + +const openForm = (type) => { + if (type === 'add') { + dialogTitle.value = "鏂板鍏憡"; + form.value = { + id: undefined, + noticeTitle: "", + noticeType: "", + noticeContent: "", + status: "0", + priority: "1", + remark: "", + createBy: userStore.name || "褰撳墠鐢ㄦ埛", + createTime: new Date().toLocaleString() + }; + } + dialogVisible.value = true; +}; + +const handleEdit = (row) => { + dialogTitle.value = "缂栬緫鍏憡"; + form.value = { ...row }; + dialogVisible.value = true; +}; + +const handleSelectionChange = (selection) => { + selectedIds.value = selection.map(item => item.id); +}; + +const handleDelete = (id) => { + ElMessageBox.confirm( + "纭鍒犻櫎杩欐潯鍏憡鍚楋紵", + "鎻愮ず", + { + confirmButtonText: "纭畾", + cancelButtonText: "鍙栨秷", + type: "warning" + } + ).then(() => { + const index = mockData.findIndex(item => item.id === id); + if (index > -1) { + mockData.splice(index, 1); + ElMessage.success("鍒犻櫎鎴愬姛"); + } + }); +}; + +const submitForm = () => { + formRef.value.validate((valid) => { + if (valid) { + if (form.value.id) { + // 缂栬緫妯″紡 + const index = mockData.findIndex(item => item.id === form.value.id); + if (index > -1) { + mockData[index] = { ...form.value }; + } + ElMessage.success("淇敼鎴愬姛"); + } else { + // 鏂板妯″紡 + const newId = Math.max(...mockData.map(item => item.id)) + 1; + const newNotice = { + ...form.value, + id: newId, + createTime: new Date().toLocaleString() + }; + mockData.unshift(newNotice); + ElMessage.success("鏂板鎴愬姛"); + } + dialogVisible.value = false; + } + }); +}; + +const resetForm = () => { + formRef.value?.resetFields(); +}; + +// 鐢熷懡鍛ㄦ湡 +onMounted(() => { + // 椤甸潰鍔犺浇瀹屾垚 +}); +</script> + +<style scoped> +.search_form { + background: #fff; + padding: 20px; + margin-bottom: 20px; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + display: flex; + justify-content: space-between; + align-items: center; +} + +.search_title { + font-weight: 500; + color: #333; + margin-right: 8px; +} + +.ml10 { + margin-left: 10px; +} + +.notice-board { + background: #f5f7fa; + padding: 20px; + border-radius: 8px; +} + +.notice-section { + margin-bottom: 30px; +} + +.section-header { + display: flex; + align-items: center; + margin-bottom: 20px; + padding: 0 10px; +} + +.section-header h3 { + margin: 0; + color: #303133; + font-size: 18px; + font-weight: 600; +} + +.section-count { + margin-left: 10px; + background: #409eff; + color: white; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; +} + +.notice-cards { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); + gap: 20px; +} + +.notice-card { + background: white; + border-radius: 12px; + padding: 20px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; + border-left: 4px solid transparent; +} + +.notice-card:hover { + transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); +} + +.holiday-card { + border-left-color: #67c23a; +} + +.maintenance-card { + border-left-color: #e6a23c; +} + +.urgent { + border-left-color: #f56c6c; + background: linear-gradient(135deg, #fff5f5 0%, #ffffff 100%); +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 15px; +} + +.card-title { + display: flex; + align-items: center; + font-size: 16px; + font-weight: 600; + color: #303133; + flex: 1; +} + +.holiday-icon { + color: #67c23a; + margin-right: 8px; + font-size: 18px; +} + +.maintenance-icon { + color: #e6a23c; + margin-right: 8px; + font-size: 18px; +} + +.card-actions { + display: flex; + gap: 8px; +} + +.card-content { + margin-bottom: 15px; +} + +.card-content p { + margin: 0; + color: #606266; + line-height: 1.6; + font-size: 14px; +} + +.card-footer { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.card-meta { + display: flex; + gap: 8px; +} + +.priority, .status { + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; +} + +.priority-1 { background: #f0f9ff; color: #0369a1; } +.priority-2 { background: #fef3c7; color: #d97706; } +.priority-3 { background: #fef2f2; color: #dc2626; } + +.status-0 { background: #f3f4f6; color: #6b7280; } +.status-1 { background: #d1fae5; color: #059669; } +.status-2 { background: #fef3c7; color: #d97706; } + +.card-info { + display: flex; + flex-direction: column; + align-items: flex-end; + font-size: 12px; + color: #909399; +} + +.creator { + font-weight: 500; + margin-bottom: 2px; +} + +.card-remark { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 12px; + background: #f8f9fa; + border-radius: 6px; + font-size: 12px; + color: #606266; + border-left: 3px solid #409eff; +} + +.empty-state { + text-align: center; + padding: 60px 20px; +} + +.dialog-footer { + text-align: right; +} + +/* 鍝嶅簲寮忚璁� */ +@media (max-width: 768px) { + .notice-cards { + grid-template-columns: 1fr; + } + + .search_form { + flex-direction: column; + gap: 15px; + } + + .search_form > div { + width: 100%; + } +} +</style> diff --git a/src/views/collaborativeApproval/warningSystem/index.vue b/src/views/collaborativeApproval/warningSystem/index.vue new file mode 100644 index 0000000..b04c583 --- /dev/null +++ b/src/views/collaborativeApproval/warningSystem/index.vue @@ -0,0 +1,307 @@ +<template> + <div class="warning-system"> + <h2>棰勮鑱斿姩鏈哄埗</h2> + + <!-- 缁熻鍗$墖 --> + <div class="stats"> + <div class="stat-card red"> + <span class="number">2</span> + <span class="label">绾㈣壊棰勮</span> + </div> + <div class="stat-card orange"> + <span class="number">1</span> + <span class="label">姗欒壊棰勮</span> + </div> + <div class="stat-card yellow"> + <span class="number">1</span> + <span class="label">榛勮壊棰勮</span> + </div> + <div class="stat-card green"> + <span class="number">1</span> + <span class="label">缁胯壊棰勮</span> + </div> + </div> + + <!-- 棰勮鍒楄〃 --> + <div class="warning-list"> + <h3>棰勮鍒楄〃</h3> + <table> + <thead> + <tr> + <th>缂栧彿</th> + <th>鏍囬</th> + <th>绫诲瀷</th> + <th>绛夌骇</th> + <th>鐘舵��</th> + <th>璐d换浜�</th> + <th>鎿嶄綔</th> + </tr> + </thead> + <tbody> + <tr v-for="warning in warnings" :key="warning.id"> + <td>{{ warning.id }}</td> + <td>{{ warning.title }}</td> + <td>{{ warning.type }}</td> + <td> + <span :class="['level-tag', warning.level]"> + {{ warning.levelText }} + </span> + </td> + <td> + <span :class="['status-tag', warning.status]"> + {{ warning.statusText }} + </span> + </td> + <td>{{ warning.responsible }}</td> + <td> + <button @click="viewDetail(warning)">鏌ョ湅璇︽儏</button> + </td> + </tr> + </tbody> + </table> + </div> + + <!-- 璇︽儏瀵硅瘽妗� --> + <div v-if="showDetail" class="modal"> + <div class="modal-content"> + <h3>棰勮璇︽儏</h3> + <div v-if="currentWarning"> + <p><strong>缂栧彿锛�</strong>{{ currentWarning.id }}</p> + <p><strong>鏍囬锛�</strong>{{ currentWarning.title }}</p> + <p><strong>绫诲瀷锛�</strong>{{ currentWarning.type }}</p> + <p><strong>绛夌骇锛�</strong>{{ currentWarning.levelText }}</p> + <p><strong>鎻忚堪锛�</strong>{{ currentWarning.description }}</p> + <p><strong>褰卞搷锛�</strong>{{ currentWarning.impact }}</p> + <p><strong>寤鸿锛�</strong>{{ currentWarning.suggestions }}</p> + </div> + <button @click="showDetail = false">鍏抽棴</button> + </div> + </div> + </div> +</template> + +<script> +export default { + name: 'WarningSystem', + data() { + return { + showDetail: false, + currentWarning: null, + warnings: [ + { + id: 'W001', + title: '椤圭洰棰勭畻瓒呮敮棰勮', + type: '璐㈠姟棰勮', + level: 'red', + levelText: '绾㈣壊棰勮', + status: 'pending', + statusText: '寰呭鐞�', + responsible: '寮犵粡鐞�', + description: 'A椤圭洰棰勭畻鎵ц鐜囧凡杈�95%锛岄璁″皢瓒呭嚭棰勭畻鑼冨洿銆�', + impact: '褰卞搷椤圭洰鏁翠綋璐㈠姟鎸囨爣锛屽彲鑳藉鑷撮」鐩簭鎹�', + suggestions: '鏆傚仠闈炲繀瑕佹敮鍑猴紝浼樺寲璧勬簮閰嶇疆锛岀敵璇烽绠楄皟鏁�' + }, + { + id: 'W002', + title: '鍚堝悓鍒版湡棰勮', + type: '鍚堣棰勮', + level: 'orange', + levelText: '姗欒壊棰勮', + status: 'processing', + statusText: '澶勭悊涓�', + responsible: '鏉庝富绠�', + description: '涓庝緵搴斿晢B鐨勫悎鍚屽皢浜�2024骞�1鏈�25鏃ュ埌鏈熴��', + impact: '褰卞搷渚涘簲閾剧ǔ瀹氭�э紝鍙兘瀵艰嚧鏈嶅姟涓柇', + suggestions: '璇勪及渚涘簲鍟嗚〃鐜帮紝鍑嗗缁鏉愭枡锛屽埗瀹氬閫夋柟妗�' + }, + { + id: 'W003', + title: '璁惧缁存姢棰勮', + type: '杩愯惀棰勮', + level: 'yellow', + levelText: '榛勮壊棰勮', + status: 'pending', + statusText: '寰呭鐞�', + responsible: '鐜嬪伐绋嬪笀', + description: '鐢熶骇绾胯澶嘋宸茶繍琛�8000灏忔椂锛屾帴杩戠淮鎶ゅ懆鏈熴��', + impact: '鍙兘褰卞搷鐢熶骇鏁堢巼鍜屼骇鍝佽川閲�', + suggestions: '瀹夋帓缁存姢鏃堕棿锛屽噯澶囧浠讹紝鍒跺畾缁存姢璁″垝' + }, + { + id: 'W004', + title: '浜哄憳閰嶇疆棰勮', + type: '杩愯惀棰勮', + level: 'green', + levelText: '缁胯壊棰勮', + status: 'resolved', + statusText: '宸茶В鍐�', + responsible: '璧礖R', + description: '鎶�鏈儴闂ㄤ汉鍛橀厤缃厖瓒筹紝椤圭洰杩涘害姝e父銆�', + impact: '鏃犺礋闈㈠奖鍝�', + suggestions: '缁х画鐩戞帶浜哄憳閰嶇疆鎯呭喌' + }, + { + id: 'W005', + title: '璐ㄩ噺浜嬫晠棰勮', + type: '杩愯惀棰勮', + level: 'red', + levelText: '绾㈣壊棰勮', + status: 'pending', + statusText: '寰呭鐞�', + responsible: '闄堟�荤洃', + description: '浜у搧D鍦ㄥ鎴风幇鍦哄嚭鐜拌川閲忛棶棰樸��', + impact: '褰卞搷瀹㈡埛婊℃剰搴︼紝鍙兘閫犳垚缁忔祹鎹熷け', + suggestions: '绔嬪嵆鍙洖闂浜у搧锛屽垎鏋愬師鍥狅紝鍒跺畾鏀硅繘鎺柦' + } + ] + } + }, + methods: { + viewDetail(warning) { + this.currentWarning = warning + this.showDetail = true + } + } +} +</script> + +<style scoped> +.warning-system { + padding: 20px; + max-width: 1200px; + margin: 0 auto; +} + +h2 { + color: #333; + margin-bottom: 30px; +} + +.stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +.stat-card { + padding: 20px; + border-radius: 8px; + color: white; + text-align: center; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); +} + +.stat-card.red { background: linear-gradient(135deg, #ff6b6b, #ee5a52); } +.stat-card.orange { background: linear-gradient(135deg, #ffa726, #ff9800); } +.stat-card.yellow { background: linear-gradient(135deg, #ffd54f, #ffc107); } +.stat-card.green { background: linear-gradient(135deg, #66bb6a, #4caf50); } + +.stat-card .number { + display: block; + font-size: 32px; + font-weight: bold; + margin-bottom: 8px; +} + +.stat-card .label { + font-size: 14px; + opacity: 0.9; +} + +.warning-list h3 { + margin-bottom: 20px; + color: #333; +} + +table { + width: 100%; + border-collapse: collapse; + background: white; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); +} + +th, td { + padding: 12px; + text-align: left; + border-bottom: 1px solid #eee; +} + +th { + background: #f8f9fa; + font-weight: 600; + color: #333; +} + +.level-tag, .status-tag { + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + color: white; +} + +.level-tag.red { background: #f56c6c; } +.level-tag.orange { background: #e6a23c; } +.level-tag.yellow { background: #e6a23c; } +.level-tag.green { background: #67c23a; } + +.status-tag.pending { background: #f56c6c; } +.status-tag.processing { background: #e6a23c; } +.status-tag.resolved { background: #67c23a; } + +button { + padding: 6px 12px; + margin: 0 4px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + background: #409eff; + color: white; +} + +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.5); + display: flex; + align-items: center; + justify-content: center; +} + +.modal-content { + background: white; + padding: 30px; + border-radius: 8px; + max-width: 600px; + width: 90%; + max-height: 80vh; + overflow-y: auto; +} + +.modal-content h3 { + margin-bottom: 20px; + color: #333; +} + +.modal-content p { + margin-bottom: 15px; + line-height: 1.6; +} + +.modal-content strong { + color: #333; +} + +.modal-content button { + background: #409eff; + color: white; + padding: 10px 20px; + font-size: 14px; +} +</style> diff --git a/src/views/demo/fakePage/index.vue b/src/views/demo/fakePage/index.vue new file mode 100644 index 0000000..42cef72 --- /dev/null +++ b/src/views/demo/fakePage/index.vue @@ -0,0 +1,248 @@ +<template> + <div class="app-container"> + <el-card shadow="never"> + <div class="toolbar"> + <el-input + v-model="query.keyword" + placeholder="鎼滅储鍚嶇О/绫诲埆" + clearable + style="width: 240px" + @keyup.enter="handleSearch" + /> + <el-select + v-model="query.status" + placeholder="鐘舵��" + clearable + style="width: 140px; margin-left: 12px" + > + <el-option label="鍚敤" value="鍚敤" /> + <el-option label="鍋滅敤" value="鍋滅敤" /> + </el-select> + <el-button type="primary" style="margin-left: 12px" @click="handleSearch">鏌ヨ</el-button> + <el-button @click="resetQuery">閲嶇疆</el-button> + <el-button type="success" plain style="float: right" @click="openCreate">鏂板</el-button> + </div> + + <el-table :data="pagedList" border style="width: 100%" height="480"> + <el-table-column prop="id" label="缂栧彿" width="90" sortable /> + <el-table-column prop="name" label="鍚嶇О" min-width="140" /> + <el-table-column prop="category" label="绫诲埆" width="120" /> + <el-table-column prop="stock" label="搴撳瓨" width="100" sortable /> + <el-table-column prop="price" label="鍗曚环(楼)" width="120"> + <template #default="scope">{{ formatPrice(scope.row.price) }}</template> + </el-table-column> + <el-table-column label="鐘舵��" width="120"> + <template #default="scope"> + <el-tag :type="scope.row.status === '鍚敤' ? 'success' : 'info'">{{ scope.row.status }}</el-tag> + </template> + </el-table-column> + <el-table-column prop="updatedAt" label="鏇存柊鏃堕棿" min-width="160" /> + <el-table-column label="鎿嶄綔" width="180" fixed="right"> + <template #default="scope"> + <el-button link type="primary" @click="openEdit(scope.row)">缂栬緫</el-button> + <el-button link type="danger" @click="handleDelete(scope.row)">鍒犻櫎</el-button> + </template> + </el-table-column> + </el-table> + + <div class="pagination"> + <el-pagination + background + layout="total, sizes, prev, pager, next, jumper" + :total="filteredList.length" + :page-sizes="[5, 10, 20, 50]" + :page-size="pager.pageSize" + :current-page="pager.pageNum" + @size-change="handleSizeChange" + @current-change="handleCurrentChange" + /> + </div> + </el-card> + + <el-dialog v-model="dialogVisible" :title="isEdit ? '缂栬緫' : '鏂板'" width="520px"> + <el-form :model="form" :rules="rules" ref="formRef" label-width="90px"> + <el-form-item label="鍚嶇О" prop="name"> + <el-input v-model="form.name" placeholder="璇疯緭鍏ュ悕绉�" /> + </el-form-item> + <el-form-item label="绫诲埆" prop="category"> + <el-select v-model="form.category" placeholder="璇烽�夋嫨绫诲埆" style="width: 100%"> + <el-option label="鍘熸枡" value="鍘熸枡" /> + <el-option label="鍗婃垚鍝�" value="鍗婃垚鍝�" /> + <el-option label="鎴愬搧" value="鎴愬搧" /> + </el-select> + </el-form-item> + <el-form-item label="搴撳瓨" prop="stock"> + <el-input v-model.number="form.stock" type="number" min="0" /> + </el-form-item> + <el-form-item label="鍗曚环(楼)" prop="price"> + <el-input v-model.number="form.price" type="number" min="0" step="0.01" /> + </el-form-item> + <el-form-item label="鐘舵��" prop="status"> + <el-radio-group v-model="form.status"> + <el-radio label="鍚敤">鍚敤</el-radio> + <el-radio label="鍋滅敤">鍋滅敤</el-radio> + </el-radio-group> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="dialogVisible = false">鍙� 娑�</el-button> + <el-button type="primary" @click="submitForm">纭� 瀹�</el-button> + </template> + </el-dialog> + </div> + +</template> + +<script setup> +import { ref, reactive, computed, nextTick } from 'vue' +import { ElMessage, ElMessageBox } from 'element-plus' + +defineOptions({ name: 'FakePage' }) + +const query = reactive({ + keyword: '', + status: '' +}) + +const pager = reactive({ + pageNum: 1, + pageSize: 10 +}) + +const allList = ref(generateMockData()) + +const filteredList = computed(() => { + const keyword = (query.keyword || '').trim() + const status = query.status + return allList.value.filter(item => { + const hitKeyword = !keyword || item.name.includes(keyword) || item.category.includes(keyword) + const hitStatus = !status || item.status === status + return hitKeyword && hitStatus + }) +}) + +const pagedList = computed(() => { + const start = (pager.pageNum - 1) * pager.pageSize + const end = start + pager.pageSize + return filteredList.value.slice(start, end) +}) + +function handleSearch() { + pager.pageNum = 1 +} + +function resetQuery() { + query.keyword = '' + query.status = '' + pager.pageNum = 1 +} + +function handleSizeChange(size) { + pager.pageSize = size + pager.pageNum = 1 +} + +function handleCurrentChange(page) { + pager.pageNum = page +} + +function formatPrice(val) { + return Number(val || 0).toFixed(2) +} + +// 鏂板/缂栬緫 +const dialogVisible = ref(false) +const isEdit = ref(false) +const formRef = ref() +const form = reactive({ id: null, name: '', category: '', stock: 0, price: 0, status: '鍚敤' }) + +const rules = { + name: [{ required: true, message: '璇疯緭鍏ュ悕绉�', trigger: 'blur' }], + category: [{ required: true, message: '璇烽�夋嫨绫诲埆', trigger: 'change' }], + stock: [{ required: true, message: '璇疯緭鍏ュ簱瀛�', trigger: 'blur' }], + price: [{ required: true, message: '璇疯緭鍏ュ崟浠�', trigger: 'blur' }] +} + +function openCreate() { + isEdit.value = false + Object.assign(form, { id: null, name: '', category: '', stock: 0, price: 0, status: '鍚敤' }) + dialogVisible.value = true + nextTick(() => formRef.value?.clearValidate?.()) +} + +function openEdit(row) { + isEdit.value = true + Object.assign(form, JSON.parse(JSON.stringify(row))) + dialogVisible.value = true + nextTick(() => formRef.value?.clearValidate?.()) +} + +function submitForm() { + formRef.value?.validate?.((valid) => { + if (!valid) return + if (isEdit.value) { + const index = allList.value.findIndex(x => x.id === form.id) + if (index > -1) { + allList.value[index] = { ...form, updatedAt: nowString() } + ElMessage.success('宸蹭繚瀛�') + } + } else { + const newId = Date.now() + allList.value.unshift({ ...form, id: newId, updatedAt: nowString() }) + ElMessage.success('宸叉柊澧�') + } + dialogVisible.value = false + }) +} + +function handleDelete(row) { + ElMessageBox.confirm(`纭鍒犻櫎銆�${row.name}銆戝悧锛焋, '鎻愮ず', { type: 'warning' }) + .then(() => { + allList.value = allList.value.filter(x => x.id !== row.id) + ElMessage.success('宸插垹闄�') + }) + .catch(() => {}) +} + +function generateMockData() { + const categories = ['鍘熸枡', '鍗婃垚鍝�', '鎴愬搧'] + const statusOptions = ['鍚敤', '鍋滅敤'] + const list = [] + for (let i = 1; i <= 36; i++) { + list.push({ + id: i, + name: `鐗╂枡-${i.toString().padStart(3, '0')}`, + category: categories[i % categories.length], + stock: Math.floor(Math.random() * 1000), + price: (Math.random() * 500 + 10).toFixed(2), + status: statusOptions[i % 2], + updatedAt: nowString() + }) + } + return list +} + +function nowString() { + const d = new Date() + const yyyy = d.getFullYear() + const MM = String(d.getMonth() + 1).padStart(2, '0') + const dd = String(d.getDate()).padStart(2, '0') + const hh = String(d.getHours()).padStart(2, '0') + const mm = String(d.getMinutes()).padStart(2, '0') + const ss = String(d.getSeconds()).padStart(2, '0') + return `${yyyy}-${MM}-${dd} ${hh}:${mm}:${ss}` +} +</script> + +<style scoped> +.toolbar { + margin-bottom: 12px; +} +.pagination { + margin-top: 12px; + text-align: right; +} +</style> + + + -- Gitblit v1.9.3