App.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. <template>
  2. <ElConfigProvider
  3. size="default"
  4. :locale="locales[language]"
  5. :z-index="3000"
  6. :card="{
  7. shadow: 'never'
  8. }"
  9. :dialog="{ alignCenter: true }"
  10. >
  11. <RouterView></RouterView>
  12. </ElConfigProvider>
  13. </template>
  14. <script setup lang="ts">
  15. import { onBeforeMount, onMounted, onBeforeUnmount } from 'vue'
  16. import { storeToRefs } from 'pinia'
  17. import { useUserStore } from './store/modules/user'
  18. import zh from 'element-plus/es/locale/lang/zh-cn'
  19. import en from 'element-plus/es/locale/lang/en'
  20. import { systemUpgrade } from './utils/sys'
  21. import { toggleTransition } from './utils/ui/animation'
  22. import { checkStorageCompatibility } from './utils/storage'
  23. import { initializeTheme } from './hooks/core/useTheme'
  24. import { refreshFilterCollapse, setupFilterCollapseObserver } from './utils/ui/filter-collapse'
  25. import { setupListTableRowSelect } from './utils/ui/list-table-row-select'
  26. const userStore = useUserStore()
  27. const { language } = storeToRefs(userStore)
  28. const locales = {
  29. zh: zh,
  30. en: en
  31. }
  32. let buttonIconObserver: MutationObserver | null = null
  33. let panelTitleObserver: MutationObserver | null = null
  34. let filterCollapseObserver: MutationObserver | null = null
  35. let teardownListTableRowSelect: (() => void) | null = null
  36. const iconClassByText: Array<[RegExp, string]> = [
  37. [/(新增|新建|创建|添加)/, 'auto-icon-plus'],
  38. [/(删除|移除|清空|关闭)/, 'auto-icon-delete'],
  39. [/(导入|上传)/, 'auto-icon-upload'],
  40. [/(导出|下载)/, 'auto-icon-download'],
  41. [/(搜索|查询|筛选)/, 'auto-icon-search'],
  42. [/(重置|刷新)/, 'auto-icon-refresh'],
  43. [/(编辑|修改|配置|设置)/, 'auto-icon-edit'],
  44. [/(查看|详情)/, 'auto-icon-view'],
  45. [/(保存|提交|确定|确认)/, 'auto-icon-check']
  46. ]
  47. const autoIconClassList = iconClassByText.map(([, cls]) => cls)
  48. const refreshButtonAutoIcons = () => {
  49. const selectors = [
  50. '.page-container .el-button:not(.is-circle):not(.el-button--text):not(:has(.el-icon))',
  51. '.console-container .el-button:not(.is-circle):not(.el-button--text):not(:has(.el-icon))'
  52. ]
  53. const buttons = document.querySelectorAll<HTMLElement>(selectors.join(','))
  54. buttons.forEach((button) => {
  55. autoIconClassList.forEach((className) => button.classList.remove(className))
  56. // 区域/状态 Tab 仅展示文案,不参与按钮自动图标
  57. if (button.classList.contains('region-tab-btn')) return
  58. const text = (button.textContent || '').replace(/\s+/g, '')
  59. const matched = iconClassByText.find(([rule]) => rule.test(text))
  60. if (matched) button.classList.add(matched[1])
  61. })
  62. }
  63. const panelTitleIconByText: Array<[RegExp, string]> = [
  64. [/(租户)/, 'auto-title-tenant'],
  65. [/(字典)/, 'auto-title-dict'],
  66. [/(角色)/, 'auto-title-role'],
  67. [/(菜单)/, 'auto-title-menu'],
  68. [/(职位)/, 'auto-title-post'],
  69. [/(站点)/, 'auto-title-location'],
  70. [/(单位)/, 'auto-title-unit'],
  71. [/(快递)/, 'auto-title-express'],
  72. [/(组织)/, 'auto-title-dept'],
  73. [/(SOP)/i, 'auto-title-file'],
  74. [/(捐赠|协议)/, 'auto-title-file'],
  75. [/(缴费|退费)/, 'auto-title-calc'],
  76. [/(接收|检查项|样本|编码)/, 'auto-title-file'],
  77. [/(审核|记录)/, 'auto-title-file'],
  78. [/(客户|公司|机构|医院)/, 'auto-title-user'],
  79. [/(采集|地区|洁净|库位)/, 'auto-title-location'],
  80. [/(预约)/, 'auto-title-operation'],
  81. [/(工单)/, 'auto-title-receipt'],
  82. [/(流程|待办|已办|申请|请假|执行|计划)/, 'auto-title-operation'],
  83. [/(耗材|试剂)/, 'auto-title-box'],
  84. [/(路线)/, 'auto-title-route'],
  85. [/(工艺)/, 'auto-title-settings'],
  86. [/(规格|价格|体系|渠道|项目)/, 'auto-title-category'],
  87. [/(类型)/, 'auto-title-category'],
  88. [/(任务|调度)/, 'auto-title-operation'],
  89. [/(区域)/, 'auto-title-location'],
  90. [/(用户|账号)/, 'auto-title-user'],
  91. [/(参数|配置)/, 'auto-title-settings'],
  92. [/(分类)/, 'auto-title-category'],
  93. [/(商品|产品)/, 'auto-title-box'],
  94. [/(订单)/, 'auto-title-receipt'],
  95. [/(发票)/, 'auto-title-file'],
  96. [/(询价|报价)/, 'auto-title-quote'],
  97. [/(结算)/, 'auto-title-calc'],
  98. [/(入口|概览|选择)/, 'auto-title-menu']
  99. ]
  100. const panelTitleClassList = [...panelTitleIconByText.map(([, cls]) => cls), 'auto-title-list']
  101. const panelTitleBarClass = 'title-bar-heading'
  102. const panelTitleHeadingSelector =
  103. '.page-container .panel-top h2, .page-container .panel-top h3, .page-container .storage-content-header h3, .page-container .storage-aside__head h2, .page-container .user-dept-aside__head h2, .console-container .panel-top h2'
  104. const refreshPanelTitleIcons = () => {
  105. const headings = document.querySelectorAll<HTMLHeadingElement>(panelTitleHeadingSelector)
  106. headings.forEach((heading) => {
  107. const text = (heading.textContent || '').replace(/\s+/g, '')
  108. const preferPanelIcon = Boolean(heading.closest('.page-container, .console-container'))
  109. if (!preferPanelIcon) return
  110. if (heading.classList.contains(panelTitleBarClass)) return
  111. heading.classList.remove(panelTitleBarClass)
  112. // 已手写 title-ic(例如 delivery-demo)则不干预
  113. if (heading.querySelector('.title-ic')) return
  114. // 确保存在容器
  115. const span = document.createElement('span')
  116. span.className = 'title-ic'
  117. span.setAttribute('aria-hidden', 'true')
  118. heading.prepend(span)
  119. // 根据标题文本匹配图标类型
  120. panelTitleClassList.forEach((className) => heading.classList.remove(className))
  121. const matched = panelTitleIconByText.find(([rule]) => rule.test(text))
  122. heading.classList.add(matched ? matched[1] : 'auto-title-list')
  123. })
  124. }
  125. onBeforeMount(() => {
  126. toggleTransition(true)
  127. initializeTheme()
  128. })
  129. onMounted(() => {
  130. checkStorageCompatibility()
  131. toggleTransition(false)
  132. systemUpgrade()
  133. refreshButtonAutoIcons()
  134. refreshPanelTitleIcons()
  135. refreshFilterCollapse()
  136. buttonIconObserver = new MutationObserver(() => {
  137. refreshButtonAutoIcons()
  138. })
  139. buttonIconObserver.observe(document.body, {
  140. childList: true,
  141. subtree: true
  142. })
  143. panelTitleObserver = new MutationObserver(() => {
  144. refreshPanelTitleIcons()
  145. })
  146. panelTitleObserver.observe(document.body, {
  147. childList: true,
  148. subtree: true
  149. })
  150. filterCollapseObserver = setupFilterCollapseObserver()
  151. teardownListTableRowSelect = setupListTableRowSelect()
  152. })
  153. onBeforeUnmount(() => {
  154. buttonIconObserver?.disconnect()
  155. buttonIconObserver = null
  156. panelTitleObserver?.disconnect()
  157. panelTitleObserver = null
  158. filterCollapseObserver?.disconnect()
  159. filterCollapseObserver = null
  160. teardownListTableRowSelect?.()
  161. teardownListTableRowSelect = null
  162. })
  163. </script>