index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. <template>
  2. <el-container
  3. v-loading="isAppLoadings" class="hc-layout-box"
  4. :class="{ 'yn-theme': !isYunNanProject, 'is-no-layout': !isNullES(isLayout) && isLayout === 'no' }"
  5. >
  6. <el-header class="hc-layout-header">
  7. <!-- 修改 logo 部分 -->
  8. <div v-if="!isYunNanProject" class="hc-layout-header-logo logo-transition" :style="`width: ${isCollapse ? '0px' : '200px'};`" @click="logoClick">
  9. <img v-show="!isCollapse" id="logo-name" :src="appLogoName" alt="" @load="handleImageLoad">
  10. </div>
  11. <div v-else class="hc-layout-header-logo logo-transition" @click="logoClick">
  12. <img id="logo-icon" :src="appLogoIcon" alt="" @load="handleImageLoad">
  13. <div class="ml-2">
  14. <div class="text-18px font-900">工程项目档案资料管理系统</div>
  15. <div class="text-12px">Engineering Project File Manegement Platform</div>
  16. </div>
  17. </div>
  18. <div class="header-top-collapse-bar" @click="collapseChange">
  19. <HcIcon v-if="isCollapse" name="menu-unfold" />
  20. <HcIcon v-else name="menu-fold" />
  21. </div>
  22. <div class="header-top-menu-bar">
  23. <HcTopMenuBar v-if="!isYunNanProject" @load="topMenuLoad" @change="topMenuChange" />
  24. </div>
  25. <div class="header-content-bar">
  26. <HcCascader @send="cascaderSend" @change="cascaderChange" />
  27. <hc-upload-bar v-if="!isYunNanProject" />
  28. <hc-upload-bar v-else style="color:black" />
  29. <HelpInfoBar v-if="!isYunNanProject" />
  30. <ConfigBar v-if="!isYunNanProject" />
  31. <UserInfoBar @load="userInfoLoad" />
  32. </div>
  33. </el-header>
  34. <el-container class="hc-layout-container">
  35. <el-aside v-if="isAsideMenu" class="hc-layout-aside" :class="[isCollapse ? 'is-collapse' : '']" :width="isCollapse ? '90px' : '200px'">
  36. <MenuBar :collapse="isCollapse" :cur="menuBarKey" :datas="menuBarData" :msg-count="msgCount" @change="menuBarChange" />
  37. </el-aside>
  38. <el-main class="hc-layout-main">
  39. <div class="hc-router-menu-bar">
  40. <RouterMenu @load="routerMenuLoad" />
  41. </div>
  42. <div id="hc-main-box" class="hc-main-page">
  43. <div class="hc-main-body">
  44. <router-view v-if="reloadRouter" v-slot="{ Component }">
  45. <transition name="fade-transform">
  46. <keep-alive :max="10">
  47. <component :is="Component" :msg-count="msgCount" />
  48. </keep-alive>
  49. </transition>
  50. </router-view>
  51. </div>
  52. </div>
  53. </el-main>
  54. </el-container>
  55. <hc-reminder v-model="isReminderShow" :text="isReminderText" />
  56. </el-container>
  57. </template>
  58. <script setup>
  59. import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
  60. import { useRoute, useRouter } from 'vue-router'
  61. import { getArrValue, getObjValue, isNullES, useClick } from 'js-fast-way'
  62. import { HcSocket } from '~src/plugins/HcSocket'
  63. import { HcAnnouncement } from 'hc-vue3-ui'
  64. import { useAppStore } from '~src/store'
  65. import { initButtons } from '~sto/app'
  66. import { useProject } from '~sto/useProject'
  67. import website from '~src/config'
  68. import appLogoIcon from '~src/assets/logo/yunnan.png'
  69. //初始组合式
  70. const router = useRouter()
  71. const useRoutes = useRoute()
  72. const store = useAppStore()
  73. const reloadRouter = ref(false)
  74. //获取项目信息
  75. const { isAppLoading } = useProject()
  76. //子组件
  77. import HcTopMenuBar from './modules/HcTopMenu.vue'
  78. import HcCascader from './modules/Cascader.vue'
  79. import UserInfoBar from './modules/UserInfoBar.vue'
  80. import HelpInfoBar from './modules/HelpInfoBar.vue'
  81. import ConfigBar from './modules/ConfigBar.vue'
  82. import RouterMenu from './modules/RouterMenu.vue'
  83. import MenuBar from '~src/layout/modules/MenuBar.vue'
  84. // logo
  85. const appLogoName = ref(store.getLogoName)
  86. //菜单数据
  87. const menuBarKey = ref('')
  88. const menuBarData = ref([])
  89. const isLayout = ref('')
  90. const projectId = ref(store.getProjectId)
  91. // 判断是否为云南项目
  92. const isYunNanProject = ref(projectId.value === '1904814720589430785')
  93. //渲染完成
  94. onMounted(async () => {
  95. const layout = useRoutes?.query?.layout, layout2 = store.isLayout
  96. isLayout.value = layout ?? layout2
  97. annRefs.value = []
  98. initButtons()
  99. // 确保初始化时加载样式
  100. currentStyle = await loadStyles()
  101. })
  102. //监听layout
  103. watch(() => [useRoutes?.query?.layout, store.isLayout], ([layout, layout2]) => {
  104. isLayout.value = layout ?? layout2
  105. }, { deep: true })
  106. //监听项目信息变化
  107. const isAppLoadings = ref(true)
  108. watch(() => isAppLoading.value, (res) => {
  109. if (!website.localModel) {
  110. reloadRouter.value = res
  111. isAppLoadings.value = !res
  112. } else {
  113. if (res) {
  114. setTimeout(() => {
  115. isAppLoadings.value = false
  116. }, 1000)
  117. } else {
  118. isAppLoadings.value = true
  119. }
  120. }
  121. }, { immediate:true })
  122. //路由信息
  123. const routerMenuLoad = ({ key }) => {
  124. menuBarKey.value = key
  125. }
  126. // 是否折叠
  127. const isCollapse = ref(false)
  128. const collapseChange = () => {
  129. const bool = !isCollapse.value
  130. isCollapse.value = bool
  131. store.setCollapse(bool)
  132. }
  133. //顶部菜单导航
  134. const isAsideMenu = ref(true)
  135. const topMenuLoad = () => {
  136. isAsideMenu.value = false
  137. }
  138. //顶部菜单导航被点击
  139. const topMenuChange = (data) => {
  140. if (!isYunNanProject.value && !isNullES(data)) {
  141. menuBarData.value = data
  142. isAsideMenu.value = true
  143. }
  144. }
  145. // 修改菜单数据的监听逻辑
  146. watch(() => store.getMenus, (val) => {
  147. if (isYunNanProject.value) {
  148. let newVal = getArrValue(val)
  149. let newArr = []
  150. for (let index = 0; index < newVal.length; index++) {
  151. const element = newVal[index]
  152. if (element.parentId === '0' && ( element.code !== 'tasks' && element.code !== 'patrol-menu' && element.code !== 'tentative-menu' )) {
  153. let arr2 = element.children
  154. if (arr2) {
  155. for (let index = 0; index < arr2.length; index++) {
  156. const element2 = arr2[index]
  157. newArr.push(element2)
  158. }
  159. }
  160. } else if (element.code === 'patrol-menu') {
  161. newArr.push(element)
  162. } else if (element.code === 'tasks') {
  163. // 先跳过 tasks,最后再添加
  164. newArr.push(element)
  165. }
  166. // 最后添加 tasks
  167. }
  168. let obj1 = newArr[newArr.length - 1]
  169. let obj2 = newArr[newArr.length - 2]
  170. newArr[newArr.length - 1] = obj2
  171. newArr[newArr.length - 2] = obj1
  172. menuBarData.value = newArr
  173. // menuBarData.value = getArrValue(val)
  174. }
  175. }, { immediate: true })
  176. //菜单被点击
  177. const menuBarChange = ({ code }) => {
  178. menuBarKey.value = code
  179. router.push({ name: code })
  180. }
  181. //消息数量
  182. const msgCount = ref({
  183. allCount: 0,
  184. taskCount: 0,
  185. messageCount: 0,
  186. messageCount_1: 0,
  187. messageCount_2: 0,
  188. messageCount_3: 0,
  189. messageCount_4: 0,
  190. messageCount_5: 0,
  191. })
  192. //用户信息
  193. const userId = ref('')
  194. const userInfoLoad = ({ user_id }) => {
  195. userId.value = user_id
  196. }
  197. //项目合同段的ID
  198. let socket
  199. const cascaderSend = async ({ projectId, contractId }) => {
  200. await useClick()
  201. if (isNullES(contractId)) {
  202. //本地模式
  203. if (website.localModel) {
  204. window.$message?.error('项目信息不存在,请联系管理员')
  205. reloadRouter.value = false
  206. }
  207. return
  208. }
  209. //本地模式
  210. if (website.localModel) {
  211. setTimeout(() => {
  212. reloadRouter.value = true
  213. }, 1000)
  214. } else {
  215. reloadRouter.value = true
  216. }
  217. //链接webSocket
  218. if (!isNullES(socket)) socket.close()
  219. socket = new HcSocket({ projectId, contractId, userId: userId.value }, (res) => {
  220. socketData(res?.data)
  221. })
  222. }
  223. //长链接消息
  224. let annUpdateRef
  225. const annRefs = ref([])
  226. const socketData = async (res) => {
  227. console.log('socket:', res)
  228. const { type, data } = getObjValue(res)
  229. if (type === 'msgUpdateMsg') {
  230. closeAnnUpdate()
  231. //内容为空时,代表公告已经取消,由于前面已经关闭,所以不再创建
  232. if (isNullES(data)) return
  233. await nextTick()
  234. //系统更新公告,直接替换
  235. annUpdateRef = await HcAnnouncement({ type: 'update', data: data })
  236. } else if (type === 'msgSystemMsg') {
  237. //内容为空时,代表公告已经取消,由于前面已经关闭,所以不再创建
  238. if (isNullES(data)) {
  239. closeAnnFun()
  240. return
  241. }
  242. await nextTick()
  243. //普通公告,追加公告
  244. const ref = await HcAnnouncement({ type: 'system', data: data })
  245. annRefs.value.push(ref)
  246. } else if (type === 'msgLink') {
  247. if (store.isLogin) {
  248. socket.send('getMsg')
  249. store.setIsLogin(false)
  250. }
  251. } else if (type === 'msgCountDown') {
  252. //倒计时更新
  253. if (isNullES(data) || data <= 0) {
  254. closeReminder()
  255. return
  256. }
  257. isReminderText.value = `系统将在${data}秒后,进行更新`
  258. setReminderText(data - 1)
  259. isReminderShow.value = true
  260. }
  261. }
  262. //倒计时
  263. let timeRef
  264. let startTime
  265. const isReminderShow = ref(false)
  266. const isReminderText = ref('')
  267. const setReminderText = (totalTime) => {
  268. startTime = performance.now()
  269. const step = () => {
  270. const elapsedTime = Math.floor((performance.now() - startTime) / 1000)
  271. const remainingTime = totalTime - elapsedTime
  272. if (remainingTime < 0) {
  273. closeReminder()
  274. return
  275. }
  276. isReminderText.value = `系统将在${remainingTime}秒后,进行更新`
  277. requestAnimationFrame(step)
  278. }
  279. requestAnimationFrame(step)
  280. }
  281. //关闭倒计时
  282. const closeReminder = () => {
  283. isReminderShow.value = false
  284. if (!isNullES(timeRef)) {
  285. clearTimeout(timeRef)
  286. timeRef = null
  287. }
  288. }
  289. // 项目切换
  290. const cascaderChange = () => {
  291. reloadRouter.value = false
  292. }
  293. //首页
  294. const logoClick = () => {
  295. router.push({ name: 'home-index' })
  296. }
  297. //关闭普通公告
  298. const closeAnnFun = () => {
  299. const refs = annRefs.value
  300. for (let i = 0; i < refs.length; i++) {
  301. if (!isNullES(refs[i])) {
  302. refs[i]?.close()
  303. }
  304. }
  305. annRefs.value = []
  306. }
  307. //关闭系统更新公告
  308. const closeAnnUpdate = () => {
  309. if (!isNullES(annUpdateRef)) {
  310. annUpdateRef.close()
  311. annUpdateRef = null
  312. }
  313. }
  314. //页面卸载
  315. onUnmounted(() => {
  316. if (!isNullES(socket)) socket.close()
  317. closeAnnFun()
  318. closeAnnUpdate()
  319. })
  320. const loadStyles = async () => {
  321. try {
  322. // 1. 首先移除所有已存在的样式
  323. const existingStyles = document.head.querySelectorAll('style[data-theme]')
  324. existingStyles.forEach(style => {
  325. document.head.removeChild(style)
  326. })
  327. // 2. 创建新的样式元素
  328. const styleElement = document.createElement('style')
  329. styleElement.type = 'text/css'
  330. // 添加标识,方便后续清理
  331. styleElement.setAttribute('data-theme', isYunNanProject.value ? 'yunnan' : 'default')
  332. // 3. 加载对应的样式模块
  333. const module = isYunNanProject.value
  334. ? await import('./test-yn/index-yn.scss?inline')
  335. : await import('./index.scss?inline')
  336. styleElement.textContent = module.default
  337. document.head.appendChild(styleElement)
  338. currentStyle = styleElement
  339. // 4. 强制重新计算样式
  340. document.documentElement.style.display = 'none'
  341. document.documentElement.offsetHeight // 触发重排
  342. document.documentElement.style.display = ''
  343. return styleElement
  344. } catch (error) {
  345. console.error('加载样式失败:', error)
  346. return null
  347. }
  348. }
  349. let currentStyle = null
  350. const imageLoaded = ref(false)
  351. const handleImageLoad = () => {
  352. imageLoaded.value = true
  353. }
  354. // 监听项目类型变化,动态切换样式
  355. watch(() => isYunNanProject.value, async (newVal) => {
  356. imageLoaded.value = false
  357. await loadStyles()
  358. console.log('样式已更新:', newVal ? 'yunnan' : 'default')
  359. }, { immediate: true })
  360. // 添加watch来更新isYunNanProject
  361. watch(() =>store.getProjectId, (newProjectId) => {
  362. isYunNanProject.value = newProjectId === '1904814720589430785'
  363. //重新加载菜单
  364. let val = store.getMenus
  365. if (!isYunNanProject.value) {
  366. return
  367. }
  368. let newVal = getArrValue(val)
  369. let newArr = []
  370. for (let index = 0; index < newVal.length; index++) {
  371. const element = newVal[index]
  372. if (element.parentId === '0' && ( element.code !== 'tasks' && element.code !== 'patrol-menu' && element.code !== 'tentative-menu' )) {
  373. let arr2 = element.children
  374. if (arr2) {
  375. for (let index = 0; index < arr2.length; index++) {
  376. const element2 = arr2[index]
  377. newArr.push(element2)
  378. }
  379. }
  380. } else if (element.code === 'patrol-menu') {
  381. newArr.push(element)
  382. } else if (element.code === 'tasks') {
  383. // 先跳过 tasks,最后再添加
  384. newArr.push(element)
  385. }
  386. // 最后添加 tasks
  387. }
  388. let obj1 = newArr[newArr.length - 1]
  389. let obj2 = newArr[newArr.length - 2]
  390. newArr[newArr.length - 1] = obj2
  391. newArr[newArr.length - 2] = obj1
  392. menuBarData.value = newArr
  393. console.log(menuBarData.value, 'menuBarData.value')
  394. isAsideMenu.value = true
  395. })
  396. </script>
  397. <style lang="scss" scoped>
  398. .logo-transition {
  399. transition: all 0.3s ease;
  400. img {
  401. max-height: 28px;
  402. transition: all 0.3s ease;
  403. will-change: transform;
  404. }
  405. &.hc-layout-header-logo {
  406. display: flex;
  407. align-items: center;
  408. height: 100%;
  409. overflow: hidden;
  410. }
  411. }
  412. // 设置图片初始状态
  413. #logo-name, #logo-icon {
  414. height: 28px;
  415. width: auto;
  416. object-fit: contain;
  417. }
  418. </style>