Files
shanxia2.0/web-admin/src/App.vue
T
2026-09-26 11:59:39 +08:00

189 lines
6.9 KiB
Vue

<template>
<ElConfigProvider
size="default"
:locale="locales[language]"
:z-index="3000"
:card="{
shadow: 'never'
}"
:dialog="{ alignCenter: true }"
>
<RouterView></RouterView>
<AttachmentPreviewHost />
</ElConfigProvider>
</template>
<script setup lang="ts">
import { onBeforeMount, onMounted, onBeforeUnmount } from 'vue'
import { storeToRefs } from 'pinia'
import AttachmentPreviewHost from '@/components/business/attachment-preview-host/index.vue'
import { useUserStore } from './store/modules/user'
import zh from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/es/locale/lang/en'
import { systemUpgrade } from './utils/sys'
import { toggleTransition } from './utils/ui/animation'
import { checkStorageCompatibility } from './utils/storage'
import { initializeTheme } from './hooks/core/useTheme'
import { refreshFilterCollapse, setupFilterCollapseObserver } from './utils/ui/filter-collapse'
import { setupListTableRowSelect } from './utils/ui/list-table-row-select'
const userStore = useUserStore()
const { language } = storeToRefs(userStore)
const locales = {
zh: zh,
en: en
}
let buttonIconObserver: MutationObserver | null = null
let panelTitleObserver: MutationObserver | null = null
let filterCollapseObserver: MutationObserver | null = null
let teardownListTableRowSelect: (() => void) | null = null
const iconClassByText: Array<[RegExp, string]> = [
[/(新增|新建|创建|添加)/, 'auto-icon-plus'],
[/(删除|移除|清空|关闭)/, 'auto-icon-delete'],
[/(导入|上传)/, 'auto-icon-upload'],
[/(导出|下载)/, 'auto-icon-download'],
[/(搜索|查询|筛选)/, 'auto-icon-search'],
[/(重置|刷新)/, 'auto-icon-refresh'],
[/(编辑|修改|配置|设置)/, 'auto-icon-edit'],
[/(查看|详情)/, 'auto-icon-view'],
[/(保存|提交|确定|确认)/, 'auto-icon-check']
]
const autoIconClassList = iconClassByText.map(([, cls]) => cls)
const refreshButtonAutoIcons = () => {
const selectors = [
'.page-container .el-button:not(.is-circle):not(.el-button--text):not(:has(.el-icon))',
'.console-container .el-button:not(.is-circle):not(.el-button--text):not(:has(.el-icon))'
]
const buttons = document.querySelectorAll<HTMLElement>(selectors.join(','))
buttons.forEach((button) => {
autoIconClassList.forEach((className) => button.classList.remove(className))
// 区域/状态 Tab 仅展示文案,不参与按钮自动图标
if (button.classList.contains('region-tab-btn')) return
const text = (button.textContent || '').replace(/\s+/g, '')
const matched = iconClassByText.find(([rule]) => rule.test(text))
if (matched) button.classList.add(matched[1])
})
}
const panelTitleIconByText: Array<[RegExp, string]> = [
[/(租户)/, 'auto-title-tenant'],
[/(字典)/, 'auto-title-dict'],
[/(角色)/, 'auto-title-role'],
[/(菜单)/, 'auto-title-menu'],
[/(职位)/, 'auto-title-post'],
[/(站点)/, 'auto-title-location'],
[/(单位)/, 'auto-title-unit'],
[/(快递)/, 'auto-title-express'],
[/(组织)/, 'auto-title-dept'],
[/(SOP)/i, 'auto-title-file'],
[/(捐赠|协议)/, 'auto-title-file'],
[/(缴费|退费)/, 'auto-title-calc'],
[/(接收|检查项|样本|编码)/, 'auto-title-file'],
[/(审核|记录)/, 'auto-title-file'],
[/(客户|公司|机构|医院)/, 'auto-title-user'],
[/(采集|地区|洁净|库位)/, 'auto-title-location'],
[/(预约)/, 'auto-title-operation'],
[/(工单)/, 'auto-title-receipt'],
[/(流程|待办|已办|申请|请假|执行|计划)/, 'auto-title-operation'],
[/(耗材|试剂)/, 'auto-title-box'],
[/(路线)/, 'auto-title-route'],
[/(工艺)/, 'auto-title-settings'],
[/(规格|价格|体系|渠道|项目)/, 'auto-title-category'],
[/(类型)/, 'auto-title-category'],
[/(任务|调度)/, 'auto-title-operation'],
[/(区域)/, 'auto-title-location'],
[/(用户|账号)/, 'auto-title-user'],
[/(参数|配置)/, 'auto-title-settings'],
[/(分类)/, 'auto-title-category'],
[/(商品|产品)/, 'auto-title-box'],
[/(订单)/, 'auto-title-receipt'],
[/(发票)/, 'auto-title-file'],
[/(询价|报价)/, 'auto-title-quote'],
[/(结算)/, 'auto-title-calc'],
[/(入口|概览|选择)/, 'auto-title-menu']
]
const panelTitleClassList = [...panelTitleIconByText.map(([, cls]) => cls), 'auto-title-list']
const panelTitleBarClass = 'title-bar-heading'
const panelTitleHeadingSelector =
'.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'
const refreshPanelTitleIcons = () => {
const headings = document.querySelectorAll<HTMLHeadingElement>(panelTitleHeadingSelector)
headings.forEach((heading) => {
const text = (heading.textContent || '').replace(/\s+/g, '')
const preferPanelIcon = Boolean(heading.closest('.page-container, .console-container'))
if (!preferPanelIcon) return
if (heading.classList.contains(panelTitleBarClass)) return
heading.classList.remove(panelTitleBarClass)
// 已手写 title-ic(例如 delivery-demo)则不干预
if (heading.querySelector('.title-ic')) return
// 确保存在容器
const span = document.createElement('span')
span.className = 'title-ic'
span.setAttribute('aria-hidden', 'true')
heading.prepend(span)
// 根据标题文本匹配图标类型
panelTitleClassList.forEach((className) => heading.classList.remove(className))
const matched = panelTitleIconByText.find(([rule]) => rule.test(text))
heading.classList.add(matched ? matched[1] : 'auto-title-list')
})
}
onBeforeMount(() => {
toggleTransition(true)
initializeTheme()
})
onMounted(() => {
checkStorageCompatibility()
toggleTransition(false)
systemUpgrade()
refreshButtonAutoIcons()
refreshPanelTitleIcons()
refreshFilterCollapse()
buttonIconObserver = new MutationObserver(() => {
refreshButtonAutoIcons()
})
buttonIconObserver.observe(document.body, {
childList: true,
subtree: true
})
panelTitleObserver = new MutationObserver(() => {
refreshPanelTitleIcons()
})
panelTitleObserver.observe(document.body, {
childList: true,
subtree: true
})
filterCollapseObserver = setupFilterCollapseObserver()
teardownListTableRowSelect = setupListTableRowSelect()
})
onBeforeUnmount(() => {
buttonIconObserver?.disconnect()
buttonIconObserver = null
panelTitleObserver?.disconnect()
panelTitleObserver = null
filterCollapseObserver?.disconnect()
filterCollapseObserver = null
teardownListTableRowSelect?.()
teardownListTableRowSelect = null
})
</script>