2025-04-03 | wujingjing | ![]() |
2025-04-03 | wujingjing | ![]() |
2025-04-03 | wujingjing | ![]() |
2025-04-03 | wujingjing | ![]() |
2025-04-03 | wujingjing | ![]() |
2025-04-03 | wujingjing | ![]() |
2025-04-03 | wujingjing | ![]() |
2025-04-03 | wujingjing | ![]() |
src/components/chat/assistant/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/components/chat/components/playBar/PlayBar.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/components/chat/components/playBar/hook/useSpeech.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/components/chat/hooks/useAssistantContentOpt.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/layout/component/header/Header.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/layout/component/main.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/layout/component/sidebar/GisMenu.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/layout/component/sidebar/Sidebar.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/project/ch/workspace/situation/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/components/chat/assistant/index.vue
@@ -45,7 +45,7 @@ <!-- #endregion --> <!-- #region ====================== è¿ç¨è¾åº ======================--> <div class="mt-3 step-list max-h-[500px] overflow-y-auto" ref="stepListRef"> <div class="mt-3 step-list max-h-[500px] overflow-y-auto bg-gray-100" ref="stepListRef"> <el-steps v-show="msg?.stepGroup?.[index].isShow" class="mt-3" direction="vertical"> <el-step :key="`template-${stepIndex}`" src/components/chat/components/playBar/PlayBar.vue
@@ -146,6 +146,18 @@ </div> </el-tooltip> <el-tooltip v-if="isSupportSpeech" placement="top" :content="recordState.isRecording ? '忢è¯é³è¾å ¥' : 'è¯é³è¾å ¥'"> <div class="cursor-pointer size-[24px] relative !z-10 rounded flex-center hover:bg-[#f2f2f2]" @click="speechClick"> <div v-if="recordState.isRecording" class="cursor-pointer flex items-center space-x-[1px]"> <div class="w-[2px] h-[6px] bg-[#0284ff] animate-[soundWave_1.5s_ease-in-out_infinite]"></div> <div class="w-[2px] h-[9px] bg-[#0284ff] animate-[soundWave_1.5s_ease-in-out_infinite_0.1s]"></div> <div class="w-[2px] h-[12px] bg-[#0284ff] animate-[soundWave_1.5s_ease-in-out_infinite_0.2s]"></div> <div class="w-[2px] h-[9px] bg-[#0284ff] animate-[soundWave_1.5s_ease-in-out_infinite_0.3s]"></div> <div class="w-[2px] h-[6px] bg-[#0284ff] animate-[soundWave_1.5s_ease-in-out_infinite_0.4s]"></div> </div> <span v-else class="ywifont ywicon-maikefeng !text-[20px]"></span> </div> </el-tooltip> <el-tooltip placement="top" content="忢çæ" v-if="isTalking"> <div class="cursor-pointer !ml-0 size-[38px] bg-[#1d86ff] relative !z-10 rounded-full flex-center" link> <div @@ -157,7 +169,7 @@ </div> </el-tooltip> <el-tooltip v-else placement="top" content="åé"> <div class="size-[38px] rounded-full bg-black flex-center" @click="emits('sendClick')"> <div class="size-[38px] rounded-full bg-black flex-center" @click="sendClick"> <img src="/static/images/wave/QueryImg.png" /> </div> </el-tooltip> @@ -207,6 +219,7 @@ import { newChatRoomClick, sidebarIsShow, toggleSidebar } from '/@/stores/chatRoom'; import MetricValuesPreview from './metricValues/MetricValuesPreview.vue'; import { useSpeech } from './hook/useSpeech'; const emits = defineEmits(['sendClick', 'stopGenClick']); const props = defineProps({ @@ -214,6 +227,13 @@ isHome: Boolean, msgList: Array, }); const sendClick = () => { if (recordState.isRecording) { cancelRecording(); } emits('sendClick'); }; const voicePageIsShow = defineModel('voicePageIsShow', { type: Boolean, @@ -246,6 +266,18 @@ commonPhraseRef.value.updatePhrase(); }; const speechClick = () => { if (recordState.isRecording) { stopRecording(); } else { startRecording(); } }; const { isSupportSpeech, startRecording, stopRecording, recordState, cancelRecording } = useSpeech({ inputText: inputValue, }); //#region ====================== 常ç¨è¯åè½ ====================== const commonPhraseRef = useCompRef(CommonPhrases); // 常ç¨è¯åè½ç¹å» src/components/chat/components/playBar/hook/useSpeech.ts
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,181 @@ import axios from 'axios'; import { ElMessage } from 'element-plus'; import type { Ref } from 'vue'; import { reactive, ref } from 'vue'; type UseSpeechProps = { inputText: Ref<string>; }; export const useSpeech = (props: UseSpeechProps) => { const { inputText } = props; const isSupportSpeech = ref(!!navigator.mediaDevices); const baiduSpeechConfig = { format: 'wav', rate: 16000, channel: 1, cuid: 'jVNmJvBApXOwDVb5aLKETdTjMK8bm3nI', token: '', dev_pid: 80001, ak: 'aV5EwAw2hb80x8kVel8NUKfF', sk: 'TKVhvcIa4rNskak0lfhPLINlxZWaCIUM', }; const recordState = reactive({ isRecording: false, audioContext: null, mediaRecorder: null as MediaRecorder | null, audioChunks: [], }); const getAccessToken = async () => { try { const response = await axios.post( `https://wi.beng35.com/api/baidu-token/oauth/2.0/token?grant_type=client_credentials&client_id=${baiduSpeechConfig.ak}&client_secret=${baiduSpeechConfig.sk}` ); baiduSpeechConfig.token = response.data.access_token; return response.data.access_token; } catch (error) { console.error('è·å access token 失败:', error); ElMessage.error('è·åè¯é³è¯å«ææå¤±è´¥'); throw error; } }; const startRecording = async () => { try { // ç¡®ä¿æ access token if (!baiduSpeechConfig.token) { await getAccessToken(); } const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); recordState.audioContext = new AudioContext({ sampleRate: 16000, latencyHint: 'interactive', }); const source = recordState.audioContext.createMediaStreamSource(stream); const processor = recordState.audioContext.createScriptProcessor(4096, 1, 1); // åå¨åå§é³é¢æ°æ® const audioData = []; processor.onaudioprocess = (e) => { const inputData = e.inputBuffer.getChannelData(0); // å° Float32Array 转æ¢ä¸º Int16Array const int16Data = new Int16Array(inputData.length); for (let i = 0; i < inputData.length; i++) { int16Data[i] = inputData[i] * 32767; } audioData.push(int16Data); }; recordState.mediaRecorder = new MediaRecorder(stream); recordState.audioChunks = []; recordState.mediaRecorder.ondataavailable = (event) => { if (event.data.size > 0) { recordState.audioChunks.push(event.data); } }; recordState.mediaRecorder.onstop = async () => { // åå¹¶ææé³é¢æ°æ® const totalLength = audioData.reduce((acc, curr) => acc + curr.length, 0); const mergedData = new Int16Array(totalLength); let offset = 0; audioData.forEach((data) => { mergedData.set(data, offset); offset += data.length; }); // å° Int16Array 转æ¢ä¸º base64 const buffer = mergedData.buffer; let base64Data = ''; let len = 0; // åæ¹å¤çå¤§åæ°æ® // ä¸ç¶ String.fromCharCode åå±å¼è¿ç®ç¬¦... ä¼çæ const chunkSize = 0.1 * 1024 * 1024; // 0.5MB chunks const uint8Array = new Uint8Array(buffer); const chunks = []; for (let i = 0; i < uint8Array.length; i += chunkSize) { const chunk = uint8Array.slice(i, i + chunkSize); chunks.push(String.fromCharCode.apply(null, chunk)); } base64Data = btoa(chunks.join('')); len = mergedData.length * 2; try { // è°ç¨ç¾åº¦è¯é³è¯å«API const response = await fetch('https://wi.beng35.com/api/baidu-speech/pro_api', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ format: 'pcm', rate: 16000, channel: 1, cuid: baiduSpeechConfig.cuid, dev_pid: 80001, // æ®éè¯è¯å« speech: base64Data, len: len, // PCM 16ä½,æ¯ä¸ªéæ ·ç¹2åè token: baiduSpeechConfig.token, }), }); const result = await response.json(); if (result.err_no === 0 && result.result && result.result.length > 0) { inputText.value = result.result[0]; // showToast('è¯é³è¯å«æå'); } else { ElMessage.error('è¯é³è¯å«å¤±è´¥ï¼' + result.err_msg); } } catch (error) { console.error('è¯é³è¯å«è¯·æ±å¤±è´¥:', error); ElMessage.error('è¯é³è¯å«è¯·æ±å¤±è´¥'); } }; recordState.mediaRecorder.start(); recordState.isRecording = true; source.connect(processor); processor.connect(recordState.audioContext.destination); } catch (error) { console.error('å½é³å¤±è´¥:', error); ElMessage.error('å½é³å¤±è´¥,è¯·æ£æ¥éº¦å 飿é'); } }; const stopRecording = () => { if (recordState.mediaRecorder && recordState.isRecording) { recordState.mediaRecorder.stop(); recordState.isRecording = false; if (recordState.audioContext) { recordState.audioContext.close(); } } }; const cancelRecording = () => { if (recordState.mediaRecorder && recordState.isRecording) { recordState.mediaRecorder.resume(); recordState.isRecording = false; if (recordState.audioContext) { recordState.audioContext.close(); } } }; return { isSupportSpeech, startRecording, stopRecording, recordState, cancelRecording, }; }; src/components/chat/hooks/useAssistantContentOpt.ts
@@ -26,11 +26,12 @@ }; const checkIsText = (item) => { const isText = item?.content?.values?.some((item) => item?.content?.type === AnswerType.Knowledge); const isText = item?.content?.values?.some((item) => item?.content?.type === AnswerType.Knowledge) || item?.conclusion?.length > 0; return isText; }; const getPlainText = (item) => { let result = ''; const knowledgeText = item.content.values .filter((item) => { const type = item?.content?.type; @@ -49,7 +50,14 @@ .join('\n\n'); return acc + answer; }, ''); return markdownToTxt(knowledgeText); const conclusionText = item.conclusion ?.filter((item) => !!item.report) .map((item) => item.report) .join('\n\n') ?? ''; result += knowledgeText + conclusionText; return markdownToTxt(result); }; const likeClick = async (item) => { src/layout/component/header/Header.vue
@@ -1,5 +1,5 @@ <template> <div class="top_text flex justify-between px-6 items-center" :class="sidebarIsShow ? 'px-6' : 'pl-[unset] pr-6'"> <div class="top_text flex justify-between px-6 items-center pl-[unset] pr-6"> <div class="flex-items-center h-full"> <div class="nav-menu"> <router-link :to="firstToPath" class="nav-item" active-class="active"> @@ -10,10 +10,10 @@ <i class="icon-park-outline-workbench"></i> 个人工ä½å° </router-link> <router-link to="/gis/situation" class="nav-item" active-class="active"> <!-- <router-link to="/gis/situation" class="nav-item" active-class="active"> <i class="icon-park-outline-system"></i> GISç³»ç» </router-link> </router-link> --> </div> </div> <el-dialog src/layout/component/main.vue
@@ -9,9 +9,9 @@ wrap-class="layout-main-scroll flex" view-class="layout-main-scroll bg-[var(--color-bg-side)] flex h100 w-full" > <WorkSpaceMenu v-show="isWorkSpace" /> <!-- <WorkSpaceMenu v-show="isWorkSpace" /> --> <div v-show="isAskAnswer || isHome"> <SideBar v-if="!isSharePage && sidebarIsShow" :isShow="sidebarIsShow" @toggleSidebar="toggleSidebar" /> <SideBar v-if="!isSharePage " :isShow="sidebarIsShow" @toggleSidebar="toggleSidebar" /> <!-- <SidebarOther v-if="!isSharePage && !sidebarIsShow" :isShow="!sidebarIsShow" @toggleSidebar="toggleSidebar" /> --> </div> <GisMenu v-show="isGis" /> src/layout/component/sidebar/GisMenu.vue
@@ -1,10 +1,17 @@ <template> <div class="pc-chat_aside flex-0 relative" :style="`width:252px;transition: 0.7s ease-in;`"> <!-- Logo é¨å --> <div class="h-[40px] w-full bg-[#0084ff] flex justify-between items-center text-white px-2"> <span>GISç³»ç»</span> <!-- <span>å®ç½GIS</span> --> <div class="aside_top"> <div class="logo flex-items-center justify-between"> <div class="flex items-center"> <img src="/static/images/logo/logoWithNoName.png" alt="logo" class="layout-logo-medium-img" /> <span class="font-extrabold text-xl text-white tracking-wide"> WIæ°´å¡æºè½å¹³å°</span> </div> </div> </div> <!-- Logo é¨å --> <!-- <div class="h-[40px] w-full bg-[#0084ff] flex justify-between items-center text-white px-2"> <span>GISç³»ç»</span> </div> --> <!-- èå项 --> <div class="menu-container"> <el-menu :default-active="activeId" class="wi-menu !w-full" :unique-opened="true" @select="handleSelect"> src/layout/component/sidebar/Sidebar.vue
@@ -1,5 +1,5 @@ <template> <div class="pc-chat_aside flex-0 relative" :style="`width: ${leftBox}px;transition: 0.7s ease-in;`"> <div class="pc-chat_aside flex-0 relative" :style="`width: ${leftWidth}px;`"> <div class="aside_top"> <div class="logo flex-items-center justify-between"> <div class="flex items-center"> @@ -8,15 +8,15 @@ </div> </div> </div> <!-- <div class="hide-sidebar" @click="toggleSidebar" v-if="!isSharePage"> <i class="text-[#fff] transition-all ywifont ywicon-zuoyoujiantou"></i> </div> --> <div class="aside_center"> <ChatRecord /> </div> <div class="aside_bottom"> <MenuList /> </div> <div class="hide-sidebar" @click="toggleSidebar"> <i class="text-[#fff] transition-all ywifont" :class="isShow ? 'ywicon-zuoyoujiantou' : 'ywicon-zuoyoujiantou1'"></i> </div> </div> </template> @@ -25,13 +25,11 @@ import { computed } from 'vue'; import ChatRecord from './components/ChatRecord.vue'; import MenuList from './components/MenuList.vue'; import { toggleSidebar } from '/@/stores/chatRoom'; const emit = defineEmits(['toggleSidebar']); const prop = defineProps(['isShow']); const leftBox = computed(() => (prop.isShow ? 252 : 198)); const leftWidth= computed(()=>prop.isShow ? 252:0) </script> <style scoped lang="scss"> @@ -52,9 +50,9 @@ box-sizing: border-box; background-color: var(--color-bg-side); overflow: visible; -webkit-transition: width 0.1s ease-in; -o-transition: width 0.1s ease-in; transition: width 0.1s ease-in; // -webkit-transition: width 0.1s ease-in; // -o-transition: width 0.1s ease-in; // transition: width 0.1s ease-in; position: relative; display: flex; flex-direction: column; src/views/project/ch/workspace/situation/index.vue
@@ -1,16 +1,491 @@ <template> <div class="workspace-situation"> <div class="workspace-situation-header"> <div class="workspace-situation-header-title"> <span>个人工ä½å°æ¦åµ</span> </div> </div> </div> <div class="workspace-container h-full"> <!-- 左侧主è¦å å®¹åº --> <div class="flex flex-col h-full overflow-hidden" :style="{ gap: layoutGap }"> <!-- 工使¦è§ --> <div class="overview-section flex-0 bg-white border border-solid border-white rounded-lg p-[20px] h-[150px]"> <div class="section-header mb-4"> <h3 class="font-bold">工使¦è§</h3> </div> <div class="overview-cards"> <!-- 管çäººæ° --> <div class="overview-card bg-[#dee8ff] rounded-lg p-4 flex items-center"> <i class="i-carbon:user-multiple text-3xl text-blue-500 mr-4"></i> <div> <div class="text-3xl font-bold text-[#4f85f6]">3</div> <div class="text-gray-600">管ç人æ°</div> </div> </div> <!-- å¾ åäºé¡¹ --> <div class="overview-card bg-[#faeaed] rounded-lg p-4 flex items-center"> <i class="i-carbon:task text-3xl text-red-500 mr-4"></i> <div> <div class="text-3xl font-bold text-red-500">3</div> <div class="text-gray-600">å¾ åäºé¡¹</div> </div> </div> <!-- é¢è¦äºé¡¹ --> <div class="overview-card bg-[#dbf2f8] rounded-lg p-4 flex items-center"> <i class="i-carbon:warning text-3xl text-cyan-500 mr-4"></i> <div> <div class="text-3xl font-bold text-cyan-500">3</div> <div class="text-gray-600">é¢è¦äºé¡¹</div> </div> </div> </div> </div> <div class="flex flex-col flex-auto" :style="{ gap: layoutGap }"> <!-- å¾ åäºé¡¹åæåèµ·çè¡¨æ ¼ --> <div class="flex h-1/2" :style="{ gap: layoutGap }"> <!-- å¾ åäºé¡¹ --> <div class="bg-white rounded-lg p-4 shadow-sm flex flex-col w-1/2"> <div class="flex justify-between items-center mb-4 flex-0"> <div class="text-lg font-bold">å¾ åäºé¡¹</div> <el-tabs v-model="todoType" class="todo-tabs"> <el-tab-pane label="å¾ åäºé¡¹" name="todo" /> <el-tab-pane label="å·²åäºé¡¹" name="done" /> </el-tabs> </div> <el-table class="flex-auto" :data="todoList" style="width: 100%" size="small"> <el-table-column prop="name" label="äºä»¶ç±»å" min-width="120"> <template #default="{ row }"> <span :class="getEventTypeClass(row.type)">{{ row.name }}</span> </template> </el-table-column> <el-table-column prop="sender" label="å起人" width="100" /> <el-table-column prop="time" label="åèµ·æ¶é´" width="100" /> <el-table-column prop="status" label="ç¶æ" width="80"> <template #default="{ row }"> <el-tag :type="getStatusType(row.status)" size="small"> {{ row.status }} </el-tag> </template> </el-table-column> <el-table-column label="æä½" width="80" fixed="right"> <template #default> <el-button type="primary" size="small" class="custom-button">åç</el-button> </template> </el-table-column> </el-table> </div> <!-- æåèµ·ç --> <div class="bg-white rounded-lg p-4 shadow-sm flex flex-col h-full w-1/2"> <div class="flex justify-between items-center mb-4 flex-0"> <div class="text-lg font-bold">æåèµ·ç</div> <el-tabs v-model="initiatedType" class="todo-tabs"> <el-tab-pane label="æåèµ·" name="initiated" /> <el-tab-pane label="ææ¶å°" name="received" /> </el-tabs> </div> <el-table :data="initiatedList" class="flex-auto" style="width: 100%" size="small"> <el-table-column prop="type" label="ç±»å" min-width="120"> <template #default="{ row }"> <span :class="getEventTypeClass(row.type)">{{ row.type }}</span> </template> </el-table-column> <el-table-column prop="sender" label="å起人" width="100" /> <el-table-column prop="time" label="åèµ·æ¶é´" width="100" /> <el-table-column prop="status" label="ç¶æ" width="80"> <template #default="{ row }"> <el-tag :type="getStatusType(row.status)" size="small"> {{ row.status }} </el-tag> </template> </el-table-column> <el-table-column label="æä½" width="80" fixed="right"> <template #default> <el-button type="primary" size="small" class="custom-button">详æ </el-button> </template> </el-table-column> </el-table> </div> </div> <!-- é¢è¦è¡¨æ ¼ --> <div class="bg-white rounded-lg p-4 shadow-sm h-1/2"> <div class="text-lg font-bold mb-4">é¢è¦</div> <el-table :data="warningList" style="width: 100%" size="small"> <el-table-column prop="studentId" label="å¦å·" min-width="120" /> <el-table-column prop="name" label="å§å" width="100" /> <el-table-column prop="type" label="å¦çç±»å" width="100" /> <el-table-column prop="guardianId" label="æ¤ç §å·" min-width="150" /> <el-table-column prop="warningType" label="é¢è¦ç±»å" width="120"> <template #default="{ row }"> <el-tag :type="getWarningType(row.warningType)" size="small"> {{ row.warningType }} </el-tag> </template> </el-table-column> <el-table-column label="æä½" width="100" fixed="right"> <template #default> <el-button type="primary" size="small" class="custom-button">æäº¤é¢è¦</el-button> </template> </el-table-column> </el-table> </div> </div> </div> <!-- å³ä¾§è¾¹æ --> <div class="sidebar h-full overflow-hidden flex flex-col" :style="{ gap: layoutGap }"> <!-- ä¸ªäººä¿¡æ¯ --> <div class="profile-card p-[20px] bg-white border border-solid border-white rounded-lg h-[150px] flex-0"> <div class="section-header mb-4"> <h3>个人信æ¯</h3> </div> <div class="profile-header flex gap-4"> <!-- <div class="avatar-placeholder"> <i class="i-carbon:user text-3xl text-gray-400"></i> </div> --> <div class="profile-info flex flex-col gap-2"> <div class="flex items-center" v-for="item in 3" :key="item"> <span class="mr-1">ç¨æ·åï¼</span> <span>wjj</span> </div> </div> <div class="profile-info flex flex-col gap-2"> <div class="flex items-center" v-for="item in 3" :key="item"> <span class="mr-1">ç¨æ·åï¼</span> <span>wjj</span> </div> </div> </div> </div> <!-- ç»è®¡å¾è¡¨ --> <div class="chart-container p-[20px] h-1/2 overflow-hidden"> <div class="chart-header"> <h3>ç»è®¡å¾è¡¨</h3> <span class="subtitle">å½å«</span> </div> <div class="chart" ref="chartRef"></div> </div> <!-- æ¥å --> <div class="calendar-container p-[20px] h-1/2 overflow-hidden"> <div class="calendar-header"> <h3>æ¶é´</h3> <div class="flex items-center gap-2"> <el-date-picker v-model="currentMonth" type="month" format="YYYYå¹´MMæ" :placeholder="'éæ©æä»½'" size="small" /> </div> </div> <div class="calendar-notice mb-0.5">æ¨ä»æ¥æ å¾ å</div> <el-calendar v-model="currentDate"> <template #dateCell="{ data }"> <div class="custom-calendar-cell"> <span class="date-text">{{ data.day.split('-')[2] }}</span> <div v-if="hasEvent(data)" class="event-dot"></div> </div> </template> </el-calendar> </div> </div> </div> </template> <script setup lang="ts" name="WorkspaceSituation"> import { ref } from 'vue'; <script setup lang="ts"> import * as echarts from 'echarts'; import { computed, onMounted, ref } from 'vue'; const layoutGap = '10px'; interface CalendarData { day: string; [key: string]: any; } // ç¶æåé const todoType = ref('todo'); const initiatedType = ref('initiated'); const currentDate = ref(new Date('2025-04-06')); const currentMonth = computed(() => { const date = currentDate.value; return date instanceof Date ? date : new Date(date); }); const chartRef = ref<HTMLElement>(); // ç¤ºä¾æ°æ® - å¢å æ´å¤æ°æ® const todoList = ref([ { name: 'å®éªå®¤æ´æ°', type: 'update', sender: 'çç¾ä¸½', time: '2021.12.06', status: 'å¾ ç¡®è®¤' }, { name: 'æ°çæå¯¼', type: 'guide', sender: 'ææ', time: '2021.12.06', status: 'æªè¯»' }, { name: 'æ´»å¨æ¥å', type: 'activity', sender: 'å¼ ä¸', time: '2021.12.06', status: '已读' }, { name: '课ç¨åæ´', type: 'update', sender: 'æå', time: '2021.12.06', status: 'å¾ ç¡®è®¤' }, { name: 'ä¼è®®éç¥', type: 'notice', sender: 'çäº', time: '2021.12.06', status: 'æªè¯»' }, { name: 'ææè®¢è´', type: 'order', sender: 'èµµå ', time: '2021.12.06', status: 'å·²å¤ç' }, { name: 'æç»©å½å ¥', type: 'grade', sender: 'åä¸', time: '2021.12.06', status: 'å¾ ç¡®è®¤' }, { name: '请å审æ¹', type: 'leave', sender: 'å¨å «', time: '2021.12.06', status: '已读' }, { name: '请å审æ¹', type: 'leave', sender: 'å¨å «', time: '2021.12.06', status: '已读' }, { name: '请å审æ¹', type: 'leave', sender: 'å¨å «', time: '2021.12.06', status: '已读' }, { name: '请å审æ¹', type: 'leave', sender: 'å¨å «', time: '2021.12.06', status: '已读' }, { name: '请å审æ¹', type: 'leave', sender: 'å¨å «', time: '2021.12.06', status: '已读' }, ]); const initiatedList = ref([ { type: 'æ´»å¨', sender: 'çç¾ä¸½', time: '2021.12.06', status: 'è¿è¡ä¸' }, { type: 'æ¥å°æ³¨å', sender: 'ææ', time: '2021.12.06', status: 'å ³é' }, { type: '课ç¨åæ´', sender: 'å¼ ä¸', time: '2021.12.06', status: 'å¾ å®¡æ ¸' }, { type: 'ä¼è®®éç¥', sender: 'æå', time: '2021.12.06', status: '已宿' }, { type: 'ææè®¢è´', sender: 'çäº', time: '2021.12.06', status: 'è¿è¡ä¸' }, { type: 'æç»©å½å ¥', sender: 'èµµå ', time: '2021.12.06', status: 'å¾ å®¡æ ¸' }, { type: '请å审æ¹', sender: 'åä¸', time: '2021.12.06', status: 'å·²æç»' }, { type: 'æ´»å¨çå', sender: 'å¨å «', time: '2021.12.06', status: 'è¿è¡ä¸' }, { type: 'æ´»å¨çå', sender: 'å¨å «', time: '2021.12.06', status: 'è¿è¡ä¸' }, { type: 'æ´»å¨çå', sender: 'å¨å «', time: '2021.12.06', status: 'è¿è¡ä¸' }, { type: 'æ´»å¨çå', sender: 'å¨å «', time: '2021.12.06', status: 'è¿è¡ä¸' }, { type: 'æ´»å¨çå', sender: 'å¨å «', time: '2021.12.06', status: 'è¿è¡ä¸' }, { type: 'æ´»å¨çå', sender: 'å¨å «', time: '2021.12.06', status: 'è¿è¡ä¸' }, { type: 'æ´»å¨çå', sender: 'å¨å «', time: '2021.12.06', status: 'è¿è¡ä¸' }, { type: 'æ´»å¨çå', sender: 'å¨å «', time: '2021.12.06', status: 'è¿è¡ä¸' }, ]); const warningList = ref([ { studentId: 'N20930498594', name: 'çç¾ä¸½', type: 'é«ä¸', guardianId: '309209382903943', warningType: 'è¿å°é¢è¦' }, { studentId: 'N20930498595', name: 'ææ', type: 'é«ä¸', guardianId: '309209382903944', warningType: '缺å¤é¢è¦' }, { studentId: 'N20930498596', name: 'å¼ ä¸', type: 'åä¸', guardianId: '309209382903945', warningType: 'æç»©é¢è¦' }, { studentId: 'N20930498597', name: 'æå', type: 'é«ä¸', guardianId: '309209382903946', warningType: 'è¡ä¸ºé¢è¦' }, { studentId: 'N20930498598', name: 'çäº', type: 'åä¸', guardianId: '309209382903947', warningType: 'è¿å°é¢è¦' }, ]); // å·¥å ·å½æ° const getEventTypeClass = (type: string) => { const classes = { update: 'text-green-500', guide: 'text-blue-500', activity: 'text-orange-500', notice: 'text-purple-500', order: 'text-cyan-500', grade: 'text-pink-500', leave: 'text-indigo-500', }; return classes[type] || ''; }; const getStatusType = (status: string) => { const types = { å¾ ç¡®è®¤: 'warning', æªè¯»: 'danger', 已读: 'info', å·²å¤ç: 'success', è¿è¡ä¸: 'primary', å ³é: 'info', å¾ å®¡æ ¸: 'warning', 已宿: 'success', å·²æç»: 'danger', }; return types[status] || 'default'; }; const getWarningType = (type: string) => { const types = { è¿å°é¢è¦: 'danger', 缺å¤é¢è¦: 'warning', æç»©é¢è¦: 'info', è¡ä¸ºé¢è¦: 'warning', }; return types[type] || 'default'; }; const hasEvent = (date: CalendarData) => { // å®ç°å¤ææ¥ææ¯å¦æäºä»¶çé»è¾ return Math.random() > 0.8; }; // å¾è¡¨åå§å onMounted(() => { if (chartRef.value) { const chart = echarts.init(chartRef.value); chart.setOption({ xAxis: { type: 'category', data: ['ä¸å½', 'å¾·å½', 'æ³å½', 'è±å½', 'æ°è¥¿å °', 'ç¾å½', 'ç士'], }, yAxis: { type: 'value', }, series: [ { data: [80, 50, 100, 30, 70, 80, 60], type: 'bar', barWidth: '30%', itemStyle: { color: '#409EFF', }, }, ], }); } }); </script> <style scoped lang="scss"></style> <style scoped lang="scss"> .workspace-container { display: grid; grid-template-columns: 1fr 300px; gap: v-bind(layoutGap); padding: 20px; background-color: #f5f7fa; } .overview-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; } // èªå®ä¹æé®æ ·å¼ .custom-button { --el-button-bg-color: var(--color-btn-base); --el-button-border-color: var(--color-btn-base); --el-button-hover-bg-color: var(--color-btn-hover); --el-button-hover-border-color: var(--color-btn-hover); } // èªå®ä¹ tabs æ ·å¼ :deep(.todo-tabs) { .el-tabs__header { margin: 0; } .el-tabs__nav-wrap::after { display: none; } .el-tabs__item { padding: 0 10px; height: 32px; line-height: 32px; font-size: 14px; &.is-active { color: var(--color-btn-base); } } .el-tabs__active-bar { background-color: var(--color-btn-base); } } // èªå®ä¹æ¥åæ ·å¼ :deep(.el-calendar) { --el-calendar-border: none; --el-calendar-header-border-bottom: none; background: none; .el-calendar__header { display: none; } .el-calendar__body { padding: 12px 0; } .el-calendar-table { td { border: none; padding: 4px; } .current { background: none; } } } .custom-calendar-cell { height: 32px; display: flex; align-items: center; justify-content: center; position: relative; .date-text { font-size: 14px; } .event-dot { position: absolute; top: 2px; right: 2px; width: 6px; height: 6px; border-radius: 50%; background-color: var(--el-color-primary); } } .calendar-notice { background-color: #fff7e6; color: #fa8c16; padding: 8px 12px; border-radius: 4px; font-size: 14px; } .avatar-placeholder { width: 64px; height: 64px; border-radius: 50%; background-color: #f5f7fa; display: flex; align-items: center; justify-content: center; } :deep(.el-calendar-table .el-calendar-day) { --el-calendar-cell-width: 10px; } .chart-container { background: white; border-radius: 8px; .chart { height: 300px; } .chart-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; .subtitle { color: var(--el-text-color-secondary); font-size: 14px; } } } .calendar-container { background: white; border-radius: 8px; .calendar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; h3 { font-size: 16px; font-weight: 500; margin: 0; } } } :deep(.el-date-picker) { --el-input-width: 120px; } .section-header { h3 { font-size: 16px; // font-weight: 500; // margin: 0; // color: var(--el-text-color-primary); } } </style>