| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script setup>
|
| | | import { ElMessage } from "element-plus"
|
| | | import Cookies from "js-cookie"
|
| | | import { encrypt, decrypt } from "@/utils/jsencrypt"
|
| | | import useUserStore from "@/store/modules/user"
|
| | | import defaultBrandLogo from "@/assets/logo/logo.png"
|
| | |
|
| | | const userStore = useUserStore()
|
| | | const route = useRoute()
|
| | | const router = useRouter()
|
| | |
|
| | | const appTitle = String(import.meta.env.VITE_APP_TITLE || "æ°åå·¥å MOM ç³»ç»").trim()
|
| | | const companySubtitle = String(import.meta.env.VITE_LOGIN_SUBTITLE || "Digital Factory Operation Center").trim()
|
| | | const configuredLogo = String(import.meta.env.VITE_APP_LOGO || "").trim()
|
| | | const logoModules = import.meta.glob("/src/assets/logo/*.png", { eager: true })
|
| | | const brandIconUrl = `${import.meta.env.BASE_URL}favicon.ico`
|
| | |
|
| | | const redirect = ref("")
|
| | | const loading = ref(false)
|
| | | const now = ref(new Date())
|
| | | const brandLogoUrl = ref(defaultBrandLogo)
|
| | |
|
| | | const loginForm = ref({
|
| | | username: "",
|
| | | password: "",
|
| | | rememberMe: false,
|
| | | })
|
| | |
|
| | | const companyName = computed(() => {
|
| | | const currentFactoryName = String(userStore.currentFactoryName || "").trim()
|
| | | return currentFactoryName || appTitle
|
| | | })
|
| | |
|
| | | const todayLabel = computed(() => {
|
| | | const date = now.value
|
| | | return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
| | | })
|
| | |
|
| | | const clockLabel = computed(() => {
|
| | | const date = now.value
|
| | | return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
| | | })
|
| | |
|
| | | watch(
|
| | | route,
|
| | | (newRoute) => {
|
| | | redirect.value = String(newRoute.query?.redirect || "")
|
| | | },
|
| | | { immediate: true }
|
| | | )
|
| | |
|
| | | watch(
|
| | | () => userStore.currentFactoryName,
|
| | | () => updateBrandLogo(),
|
| | | { immediate: true }
|
| | | )
|
| | |
|
| | | let timer = 0
|
| | | onMounted(() => {
|
| | | timer = window.setInterval(() => {
|
| | | now.value = new Date()
|
| | | }, 1000)
|
| | | })
|
| | |
|
| | | onBeforeUnmount(() => {
|
| | | if (timer) {
|
| | | window.clearInterval(timer)
|
| | | timer = 0
|
| | | }
|
| | | })
|
| | |
|
| | | function pad(value) {
|
| | | return String(value).padStart(2, "0")
|
| | | }
|
| | |
|
| | | function resolveConfiguredLogo() {
|
| | | if (!configuredLogo) {
|
| | | return ""
|
| | | }
|
| | |
|
| | | if (/^(https?:)?\/\//.test(configuredLogo) || configuredLogo.startsWith("data:")) {
|
| | | return configuredLogo
|
| | | }
|
| | |
|
| | | const cleanPath = configuredLogo.replace(/^\/+/, "")
|
| | | const fullPath = cleanPath.startsWith("src/") ? `/${cleanPath}` : `/src/${cleanPath}`
|
| | | const localLogo = logoModules[fullPath]
|
| | |
|
| | | if (localLogo && localLogo.default) {
|
| | | return localLogo.default
|
| | | }
|
| | |
|
| | | if (configuredLogo.startsWith("/")) {
|
| | | return configuredLogo
|
| | | }
|
| | |
|
| | | return `${import.meta.env.BASE_URL}${cleanPath}`
|
| | | }
|
| | |
|
| | | function updateBrandLogo() {
|
| | | const logoFromConfig = resolveConfiguredLogo()
|
| | | if (logoFromConfig) {
|
| | | brandLogoUrl.value = logoFromConfig
|
| | | return
|
| | | }
|
| | |
|
| | | const currentFactoryName = String(userStore.currentFactoryName || "").trim()
|
| | | if (!currentFactoryName) {
|
| | | brandLogoUrl.value = defaultBrandLogo
|
| | | return
|
| | | }
|
| | |
|
| | | const factoryLogoPath = `/src/assets/logo/${currentFactoryName}.png`
|
| | | const matched = logoModules[factoryLogoPath]
|
| | | brandLogoUrl.value = matched && matched.default ? matched.default : defaultBrandLogo
|
| | | }
|
| | |
|
| | | function handleLogoError() {
|
| | | brandLogoUrl.value = defaultBrandLogo
|
| | | }
|
| | |
|
| | | function handleRememberCookie() {
|
| | | if (!loginForm.value.rememberMe) {
|
| | | Cookies.remove("username")
|
| | | Cookies.remove("password")
|
| | | Cookies.remove("rememberMe")
|
| | | return
|
| | | }
|
| | |
|
| | | Cookies.set("username", loginForm.value.username, { expires: 30 })
|
| | | Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 })
|
| | | Cookies.set("rememberMe", "true", { expires: 30 })
|
| | | }
|
| | |
|
| | | function getCookie() {
|
| | | const username = Cookies.get("username")
|
| | | const password = Cookies.get("password")
|
| | | const rememberMe = Cookies.get("rememberMe")
|
| | |
|
| | | loginForm.value.username = username || ""
|
| | | loginForm.value.password = password ? decrypt(password) : ""
|
| | | loginForm.value.rememberMe = rememberMe === "true"
|
| | | }
|
| | |
|
| | | function handleLogin() {
|
| | | if (!loginForm.value.username) {
|
| | | ElMessage.error("请è¾å
¥è´¦å·")
|
| | | return
|
| | | }
|
| | | if (!loginForm.value.password) {
|
| | | ElMessage.error("请è¾å
¥å¯ç ")
|
| | | return
|
| | | }
|
| | |
|
| | | loading.value = true
|
| | | handleRememberCookie()
|
| | | userStore
|
| | | .loginCheckFactory(loginForm.value)
|
| | | .then(() => {
|
| | | const query = route.query
|
| | | const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
|
| | | if (cur !== "redirect") {
|
| | | acc[cur] = query[cur]
|
| | | }
|
| | | return acc
|
| | | }, {})
|
| | | router.push({ path: redirect.value || "/", query: otherQueryParams })
|
| | | })
|
| | | .catch(() => {})
|
| | | .finally(() => {
|
| | | loading.value = false
|
| | | })
|
| | | }
|
| | |
|
| | | getCookie()
|
| | | </script>
|
| | |
|
| | |
| | | padding: 0; |
| | | font-size: 0; |
| | | background: var(--sidebar-bg); |
| | | border-right: 1px solid rgba(255, 255, 255, 0.08); |
| | | box-shadow: 8px 0 24px rgba(15, 23, 42, 0.08); |
| | | isolation: isolate; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: -28% -52% -18% -38%; |
| | | z-index: 0; |
| | | pointer-events: none; |
| | | background: |
| | | radial-gradient(circle at 9% 12%, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.62), transparent 44%), |
| | | radial-gradient(circle at 87% 18%, rgba(56, 189, 248, 0.4), transparent 48%), |
| | | radial-gradient(circle at 20% 82%, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.3), transparent 43%), |
| | | radial-gradient(circle at 66% 62%, rgba(125, 211, 252, 0.24), transparent 50%), |
| | | conic-gradient( |
| | | from 210deg at 58% 38%, |
| | | rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.14) 0deg, |
| | | rgba(56, 189, 248, 0.05) 76deg, |
| | | rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.16) 180deg, |
| | | rgba(125, 211, 252, 0.04) 290deg, |
| | | rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.14) 360deg |
| | | ); |
| | | filter: blur(7px) saturate(1.24) contrast(1.05); |
| | | opacity: 0.96; |
| | | transform: translate3d(0, 0, 0); |
| | | transform-origin: 44% 58%; |
| | | animation: |
| | | sidebarAuroraDrift 17.9s cubic-bezier(0.31, 0.03, 0.18, 0.99) infinite, |
| | | sidebarAuroraBreath 9.7s ease-in-out infinite, |
| | | sidebarAuroraSkew 6.9s steps(23, end) infinite; |
| | | } |
| | | |
| | | &::after { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: 0; |
| | | z-index: 0; |
| | | pointer-events: none; |
| | | background: |
| | | linear-gradient( |
| | | 108deg, |
| | | transparent 10%, |
| | | rgba(255, 255, 255, 0.17) 34%, |
| | | rgba(255, 255, 255, 0.04) 48%, |
| | | transparent 72% |
| | | ), |
| | | linear-gradient( |
| | | 202deg, |
| | | rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.24) 0%, |
| | | transparent 34%, |
| | | rgba(56, 189, 248, 0.18) 66%, |
| | | transparent 100% |
| | | ), |
| | | radial-gradient(circle at 74% 12%, rgba(125, 211, 252, 0.25), transparent 50%), |
| | | radial-gradient(circle at 22% 84%, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.14), transparent 56%); |
| | | background-size: 236% 100%, 186% 186%, 164% 164%, 180% 180%; |
| | | background-position: 224% 0, 14% 16%, 78% 10%, 18% 82%; |
| | | opacity: 0.52; |
| | | transform: translate3d(0, 0, 0); |
| | | animation: |
| | | sidebarSheenSweep 13.1s linear infinite, |
| | | sidebarSheenJitter 4.7s steps(31, end) infinite; |
| | | } |
| | | border-right: 1px solid var(--surface-border); |
| | | box-shadow: var(--shadow-md); |
| | | |
| | | > * { |
| | | position: relative; |
| | |
| | | |
| | | &.has-logo { |
| | | .el-scrollbar { |
| | | height: calc(100% - 78px); |
| | | height: calc(100% - 64px); |
| | | } |
| | | } |
| | | |
| | |
| | | border: none !important; |
| | | height: 100%; |
| | | width: 100% !important; |
| | | padding: 10px 0 16px; |
| | | border-radius: 0; |
| | | padding: 12px 0; |
| | | background: transparent !important; |
| | | box-shadow: none; |
| | | backdrop-filter: none; |
| | | } |
| | | |
| | | .el-menu-item, |
| | | .el-sub-menu__title, |
| | | .menu-title { |
| | | overflow: hidden !important; |
| | | text-overflow: ellipsis !important; |
| | | white-space: nowrap !important; |
| | | } |
| | | |
| | | .el-menu-item .el-menu-tooltip__trigger { |
| | | display: inline-flex !important; |
| | | width: 100%; |
| | | .submenu-title-noDropdown, |
| | | .el-menu-item { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .submenu-title-noDropdown, |
| | | .el-sub-menu__title, |
| | | .el-menu-item { |
| | | min-width: 0 !important; |
| | | width: calc(100% - 24px) !important; |
| | | margin: 0 12px 8px !important; |
| | | height: 50px; |
| | | line-height: 50px; |
| | | border-radius: 14px; |
| | | width: calc(100% - 16px) !important; |
| | | margin: 4px 8px !important; |
| | | height: 44px; |
| | | line-height: 44px; |
| | | border-radius: var(--radius-md); |
| | | padding-left: 16px !important; |
| | | padding-right: 36px !important; |
| | | padding-right: 36px !important; // é¢çç®å¤´ä½ç½® |
| | | box-sizing: border-box; |
| | | transition: all 0.28s ease; |
| | | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
| | | color: var(--sidebar-text); |
| | | background: linear-gradient(128deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.01)); |
| | | border: 1px solid rgba(255, 255, 255, 0.06) !important; |
| | | background: transparent; |
| | | border: none !important; |
| | | position: relative; |
| | | overflow: hidden; |
| | | |
| | | .svg-icon { |
| | | margin-right: 12px; |
| | | width: 18px; |
| | | height: 18px; |
| | | vertical-align: middle; |
| | | flex-shrink: 0; |
| | | transition: transform 0.3s ease; |
| | | } |
| | | } |
| | | |
| | | .submenu-title-noDropdown::after, |
| | | .el-sub-menu__title::after, |
| | | .el-menu-item::after { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: 0; |
| | | background: linear-gradient(115deg, transparent 12%, rgba(255, 255, 255, 0.16), transparent 78%); |
| | | transform: translateX(-100%); |
| | | opacity: 0; |
| | | transition: transform 0.45s ease, opacity 0.26s ease; |
| | | pointer-events: none; |
| | | .el-sub-menu { |
| | | &.is-opened { |
| | | > .el-sub-menu__title { |
| | | color: var(--menu-active-text) !important; |
| | | |
| | | .el-sub-menu__icon-arrow { |
| | | transform: rotate(180deg) !important; |
| | | color: var(--menu-active-text) !important; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .el-sub-menu__icon-arrow { |
| | | position: absolute !important; |
| | | right: 16px !important; |
| | | top: 50% !important; |
| | | margin-top: -6px !important; |
| | | width: 12px !important; |
| | | height: 12px !important; |
| | | font-size: 12px !important; |
| | | display: inline-block !important; |
| | | transition: transform 0.3s ease !important; |
| | | color: var(--sidebar-text) !important; |
| | | z-index: 2; |
| | | } |
| | | } |
| | | |
| | | .submenu-title-noDropdown:hover, |
| | | .el-sub-menu__title:hover, |
| | | .el-menu-item:hover { |
| | | background: linear-gradient(128deg, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.28), rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.08)) !important; |
| | | border-color: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.32) !important; |
| | | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 8px 18px rgba(8, 36, 76, 0.24); |
| | | transform: translateX(3px); |
| | | background: var(--menu-hover) !important; |
| | | color: var(--menu-active-text) !important; |
| | | |
| | | .svg-icon { |
| | | transform: scale(1.1); |
| | | } |
| | | } |
| | | |
| | | .submenu-title-noDropdown:hover::after, |
| | | .el-sub-menu__title:hover::after, |
| | | .el-menu-item:hover::after, |
| | | .el-menu-item.is-active::after, |
| | | .el-sub-menu.is-active > .el-sub-menu__title::after { |
| | | transform: translateX(100%); |
| | | opacity: 1; |
| | | } |
| | | .el-menu-item.is-active, |
| | | .el-sub-menu.is-active > .el-sub-menu__title { |
| | | color: var(--menu-active-text) !important; |
| | | background: var(--menu-active-bg) !important; |
| | | box-shadow: var(--menu-active-glow); |
| | | font-weight: 600; |
| | | |
| | | & .theme-light .is-active > .el-sub-menu__title, |
| | | & .theme-dark .is-active > .el-sub-menu__title, |
| | | & .el-menu-item.is-active { |
| | | color: #fff !important; |
| | | background: var(--menu-active-bg, linear-gradient(135deg, var(--el-color-primary), var(--el-color-primary-light-3))) !important; |
| | | background-size: 180% 180%; |
| | | box-shadow: var(--menu-active-glow, 0 10px 24px rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.34)); |
| | | border-color: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.5) !important; |
| | | animation: sidebarActiveFlow 4.6s ease infinite; |
| | | .svg-icon { |
| | | color: var(--menu-active-text) !important; |
| | | } |
| | | } |
| | | |
| | | & .nest-menu .el-sub-menu > .el-sub-menu__title, |
| | | & .el-sub-menu .el-menu-item { |
| | | min-width: 0 !important; |
| | | width: calc(100% - 24px) !important; |
| | | margin: 0 12px 8px !important; |
| | | height: 46px; |
| | | line-height: 46px; |
| | | padding-left: 14px !important; |
| | | padding-right: 14px !important; |
| | | border-radius: 12px; |
| | | transition: all 0.24s ease; |
| | | color: var(--sidebar-text); |
| | | border: 1px solid rgba(255, 255, 255, 0.06) !important; |
| | | background: linear-gradient(128deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01)); |
| | | |
| | | &:hover { |
| | | background: linear-gradient(128deg, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.24), rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.07)) !important; |
| | | border-color: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.3) !important; |
| | | transform: translateX(2px); |
| | | } |
| | | height: 40px; |
| | | line-height: 40px; |
| | | margin: 2px 8px !important; |
| | | padding-left: 44px !important; // å¢å åèåç¼©è¿ |
| | | border-radius: var(--radius-sm); |
| | | font-size: 13px; |
| | | |
| | | &.is-active { |
| | | background: var(--menu-active-bg, linear-gradient(135deg, var(--el-color-primary), var(--el-color-primary-light-3))) !important; |
| | | background-size: 180% 180%; |
| | | color: #fff !important; |
| | | font-weight: 500; |
| | | box-shadow: var(--menu-active-glow, 0 10px 24px rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.34)); |
| | | animation: sidebarActiveFlow 4.6s ease infinite; |
| | | background: var(--menu-active-bg) !important; |
| | | } |
| | | } |
| | | } |
| | |
| | | .hideSidebar { |
| | | .sidebar-container { |
| | | width: var(--sidebar-collapsed-width) !important; |
| | | |
| | | .el-menu-item, |
| | | .el-sub-menu__title { |
| | | padding: 0 !important; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin: 4px 8px !important; |
| | | width: calc(var(--sidebar-collapsed-width) - 16px) !important; |
| | | |
| | | .svg-icon { |
| | | margin-right: 0; |
| | | } |
| | | |
| | | .menu-title, |
| | | .el-sub-menu__icon-arrow { |
| | | display: none; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .main-container { |
| | | margin-left: var(--sidebar-collapsed-width); |
| | | } |
| | | |
| | | .submenu-title-noDropdown { |
| | | padding: 0 !important; |
| | | position: relative; |
| | | display: flex !important; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | .svg-icon { |
| | | margin-right: 0; |
| | | } |
| | | |
| | | .el-tooltip { |
| | | padding: 0 !important; |
| | | display: inline-flex !important; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 100%; |
| | | |
| | | .svg-icon { |
| | | margin-left: 0; |
| | | } |
| | | } |
| | | |
| | | .el-menu-tooltip__trigger { |
| | | width: 100%; |
| | | display: inline-flex !important; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | .svg-icon { |
| | | width: 22px; |
| | | height: 22px; |
| | | margin-right: 0; |
| | | flex-shrink: 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .el-sub-menu { |
| | | overflow: hidden; |
| | | |
| | | & > .el-sub-menu__title { |
| | | padding: 0 !important; |
| | | display: flex !important; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | .svg-icon { |
| | | margin-left: 0; |
| | | margin-right: 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .el-menu--collapse { |
| | | width: 100% !important; |
| | | padding: 12px 0 16px; |
| | | |
| | | > .el-menu-item, |
| | | .el-sub-menu { |
| | | & > .el-sub-menu__title, |
| | | &.el-menu-item { |
| | | width: calc(100% - 12px) !important; |
| | | margin: 0 6px 8px !important; |
| | | padding-left: 0 !important; |
| | | padding-right: 0 !important; |
| | | box-sizing: border-box; |
| | | display: flex !important; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | .svg-icon { |
| | | width: 22px; |
| | | height: 22px; |
| | | margin-right: 0; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | & > span { |
| | | height: 0; |
| | | width: 0; |
| | | overflow: hidden; |
| | | visibility: hidden; |
| | | display: inline-block; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | .el-menu--vertical { |
| | | & > .el-menu { |
| | | .svg-icon { |
| | | margin-right: 10px; |
| | | margin-right: 12px; |
| | | } |
| | | } |
| | | |
| | | .nest-menu .el-sub-menu > .el-sub-menu__title, |
| | | .el-menu-item { |
| | | min-width: 0 !important; |
| | | margin: 0 10px 8px; |
| | | margin: 4px 10px; |
| | | width: calc(100% - 20px); |
| | | height: 46px; |
| | | line-height: 46px; |
| | | padding-left: 12px !important; |
| | | padding-right: 12px !important; |
| | | height: 44px; |
| | | line-height: 44px; |
| | | padding-left: 16px !important; |
| | | padding-right: 16px !important; |
| | | box-sizing: border-box; |
| | | border-radius: 12px; |
| | | border-radius: var(--radius-md); |
| | | color: var(--sidebar-text); |
| | | border: 1px solid rgba(255, 255, 255, 0.06) !important; |
| | | background: linear-gradient(128deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.01)); |
| | | transition: all 0.24s ease; |
| | | background: transparent; |
| | | transition: all 0.2s ease; |
| | | |
| | | &:hover { |
| | | background: linear-gradient(128deg, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.24), rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.07)) !important; |
| | | border-color: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.3) !important; |
| | | transform: translateX(2px); |
| | | background: var(--menu-hover) !important; |
| | | color: #fff !important; |
| | | } |
| | | |
| | | &.is-active { |
| | | background: var(--menu-active-bg, linear-gradient(135deg, var(--el-color-primary), var(--el-color-primary-light-3))) !important; |
| | | background-size: 180% 180%; |
| | | color: #fff !important; |
| | | border-radius: 12px; |
| | | box-shadow: var(--menu-active-glow, 0 10px 24px rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.34)); |
| | | border-color: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.5) !important; |
| | | animation: sidebarActiveFlow 4.6s ease infinite; |
| | | background: var(--menu-active-bg) !important; |
| | | color: var(--menu-active-text) !important; |
| | | font-weight: 600; |
| | | box-shadow: var(--menu-active-glow); |
| | | } |
| | | } |
| | | |
| | | > .el-menu--popup { |
| | | max-height: 100vh; |
| | | overflow: hidden; |
| | | padding: 10px; |
| | | border-radius: 14px; |
| | | position: relative; |
| | | isolation: isolate; |
| | | border: 1px solid rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.26); |
| | | box-shadow: |
| | | 0 18px 40px rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.16), |
| | | var(--shadow-md); |
| | | padding: 8px; |
| | | border-radius: var(--radius-lg); |
| | | border: 1px solid var(--surface-border); |
| | | box-shadow: var(--shadow-menu); |
| | | background: var(--sidebar-bg); |
| | | backdrop-filter: blur(16px); |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: -28% -52% -18% -38%; |
| | | z-index: 0; |
| | | pointer-events: none; |
| | | background: |
| | | radial-gradient(circle at 9% 12%, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.62), transparent 44%), |
| | | radial-gradient(circle at 87% 18%, rgba(56, 189, 248, 0.4), transparent 48%), |
| | | radial-gradient(circle at 20% 82%, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.3), transparent 43%), |
| | | radial-gradient(circle at 66% 62%, rgba(125, 211, 252, 0.24), transparent 50%), |
| | | conic-gradient( |
| | | from 210deg at 58% 38%, |
| | | rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.14) 0deg, |
| | | rgba(56, 189, 248, 0.05) 76deg, |
| | | rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.16) 180deg, |
| | | rgba(125, 211, 252, 0.04) 290deg, |
| | | rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.14) 360deg |
| | | ); |
| | | filter: blur(7px) saturate(1.24) contrast(1.05); |
| | | opacity: 0.96; |
| | | transform: translate3d(0, 0, 0); |
| | | transform-origin: 44% 58%; |
| | | animation: |
| | | sidebarAuroraDrift 17.9s cubic-bezier(0.31, 0.03, 0.18, 0.99) infinite, |
| | | sidebarAuroraBreath 9.7s ease-in-out infinite, |
| | | sidebarAuroraSkew 6.9s steps(23, end) infinite; |
| | | } |
| | | |
| | | &::after { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: 0; |
| | | z-index: 0; |
| | | pointer-events: none; |
| | | background: |
| | | linear-gradient( |
| | | 108deg, |
| | | transparent 10%, |
| | | rgba(255, 255, 255, 0.17) 34%, |
| | | rgba(255, 255, 255, 0.04) 48%, |
| | | transparent 72% |
| | | ), |
| | | linear-gradient( |
| | | 202deg, |
| | | rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.24) 0%, |
| | | transparent 34%, |
| | | rgba(56, 189, 248, 0.18) 66%, |
| | | transparent 100% |
| | | ), |
| | | radial-gradient(circle at 74% 12%, rgba(125, 211, 252, 0.25), transparent 50%), |
| | | radial-gradient(circle at 22% 84%, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.14), transparent 56%); |
| | | background-size: 236% 100%, 186% 186%, 164% 164%, 180% 180%; |
| | | background-position: 224% 0, 14% 16%, 78% 10%, 18% 82%; |
| | | opacity: 0.52; |
| | | transform: translate3d(0, 0, 0); |
| | | animation: |
| | | sidebarSheenSweep 13.1s linear infinite, |
| | | sidebarSheenJitter 4.7s steps(31, end) infinite; |
| | | } |
| | | |
| | | > * { |
| | | position: relative; |
| | | z-index: 1; |
| | | } |
| | | |
| | | > .el-menu { |
| | | max-height: calc(100vh - 20px); |
| | | max-height: calc(100vh - 16px); |
| | | overflow-y: auto; |
| | | overflow-x: hidden; |
| | | |
| | | &::-webkit-scrollbar-track-piece { |
| | | background: var(--surface-muted); |
| | | } |
| | | background: transparent !important; |
| | | |
| | | &::-webkit-scrollbar { |
| | | width: 5px; |
| | | width: 4px; |
| | | } |
| | | |
| | | &::-webkit-scrollbar-thumb { |
| | | background: var(--accent-light); |
| | | border-radius: 10px; |
| | | background: var(--sidebar-muted); |
| | | border-radius: 4px; |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | 100% { |
| | | background-position: 0% 50%; |
| | | } |
| | | } |
| | | |
| | | @keyframes sidebarAuroraDrift { |
| | | 0% { |
| | | transform: translate3d(-6.3%, -1.8%, 0) scale(1.05) rotate(-1.8deg); |
| | | } |
| | | 6% { |
| | | transform: translate3d(2.2%, -4.6%, 0) scale(1.08) rotate(0.7deg); |
| | | } |
| | | 17% { |
| | | transform: translate3d(-3.7%, 4.4%, 0) scale(1.11) rotate(2deg); |
| | | } |
| | | 27% { |
| | | transform: translate3d(5.6%, 1.2%, 0) scale(1.03) rotate(-1deg); |
| | | } |
| | | 39% { |
| | | transform: translate3d(-4.8%, -3.1%, 0) scale(1.09) rotate(1.5deg); |
| | | } |
| | | 52% { |
| | | transform: translate3d(2.9%, 4.8%, 0) scale(1.04) rotate(-1.4deg); |
| | | } |
| | | 64% { |
| | | transform: translate3d(-6.4%, 0.3%, 0) scale(1.08) rotate(0.5deg); |
| | | } |
| | | 73% { |
| | | transform: translate3d(4.8%, -3.9%, 0) scale(1.05) rotate(1.6deg); |
| | | } |
| | | 81% { |
| | | transform: translate3d(-2.4%, 2.9%, 0) scale(1.1) rotate(-0.8deg); |
| | | } |
| | | 92% { |
| | | transform: translate3d(3.7%, -1.7%, 0) scale(1.06) rotate(-1.6deg); |
| | | } |
| | | 100% { |
| | | transform: translate3d(-5.9%, 0.8%, 0) scale(1.08) rotate(1.2deg); |
| | | } |
| | | } |
| | | |
| | | @keyframes sidebarAuroraBreath { |
| | | 0% { |
| | | opacity: 0.76; |
| | | filter: blur(5px) saturate(1.08); |
| | | } |
| | | 15% { |
| | | opacity: 1; |
| | | filter: blur(7px) saturate(1.28); |
| | | } |
| | | 37% { |
| | | opacity: 0.84; |
| | | filter: blur(8px) saturate(1.12); |
| | | } |
| | | 61% { |
| | | opacity: 0.98; |
| | | filter: blur(6px) saturate(1.24); |
| | | } |
| | | 83% { |
| | | opacity: 0.86; |
| | | filter: blur(7px) saturate(1.16); |
| | | } |
| | | 100% { |
| | | opacity: 0.94; |
| | | filter: blur(6px) saturate(1.2); |
| | | } |
| | | } |
| | | |
| | | @keyframes sidebarAuroraSkew { |
| | | 0% { |
| | | transform-origin: 44% 58%; |
| | | } |
| | | 21% { |
| | | transform-origin: 62% 42%; |
| | | } |
| | | 43% { |
| | | transform-origin: 31% 66%; |
| | | } |
| | | 66% { |
| | | transform-origin: 68% 74%; |
| | | } |
| | | 100% { |
| | | transform-origin: 39% 45%; |
| | | } |
| | | } |
| | | |
| | | @keyframes sidebarSheenSweep { |
| | | 0% { |
| | | background-position: 232% 0, 10% 18%, 80% 12%, 20% 82%; |
| | | } |
| | | 8% { |
| | | background-position: 186% 0, 16% 30%, 74% 18%, 28% 74%; |
| | | } |
| | | 21% { |
| | | background-position: 116% 0, 34% 10%, 62% 26%, 18% 64%; |
| | | } |
| | | 37% { |
| | | background-position: 52% 0, 50% 24%, 46% 12%, 32% 58%; |
| | | } |
| | | 52% { |
| | | background-position: -4% 0, 34% 54%, 22% 22%, 12% 46%; |
| | | } |
| | | 69% { |
| | | background-position: -62% 0, 14% 36%, 32% 34%, 24% 56%; |
| | | } |
| | | 84% { |
| | | background-position: -106% 0, 20% 20%, 46% 20%, 34% 70%; |
| | | } |
| | | 100% { |
| | | background-position: -136% 0, 10% 18%, 80% 12%, 20% 82%; |
| | | } |
| | | } |
| | | |
| | | @keyframes sidebarSheenJitter { |
| | | 0% { |
| | | opacity: 0.28; |
| | | transform: translate3d(0, 0, 0); |
| | | } |
| | | 17% { |
| | | opacity: 0.56; |
| | | transform: translate3d(1.8%, -0.5%, 0); |
| | | } |
| | | 38% { |
| | | opacity: 0.34; |
| | | transform: translate3d(-1.2%, 0.8%, 0); |
| | | } |
| | | 63% { |
| | | opacity: 0.6; |
| | | transform: translate3d(2.3%, -0.3%, 0); |
| | | } |
| | | 81% { |
| | | opacity: 0.3; |
| | | transform: translate3d(-1.6%, 0.7%, 0); |
| | | } |
| | | 100% { |
| | | opacity: 0.52; |
| | | transform: translate3d(2%, -0.1%, 0); |
| | | } |
| | | } |
| | |
| | | --content-radius: 16px; |
| | | --layout-header-z: 20; |
| | | |
| | | --el-color-primary: #2563eb; |
| | | --el-color-primary-rgb: 37, 99, 235; |
| | | --el-color-primary: #374d77; |
| | | --el-color-primary-rgb: 37, 89, 163; |
| | | --el-color-success: #14b8a6; |
| | | --el-color-warning: #f59e0b; |
| | | --el-color-danger: #ef4444; |
| | | |
| | | --sidebar-bg: linear-gradient(180deg, #0e2a4f 0%, #123e69 55%, #0e2a4f 100%); |
| | | --sidebar-text: rgba(234, 242, 255, 0.82); |
| | | --sidebar-muted: rgba(234, 242, 255, 0.82); |
| | | --menu-hover: rgba(147, 197, 253, 0.2); |
| | | --menu-active-bg: linear-gradient(135deg, #2f80ff 0%, #38bdf8 100%); |
| | | --menu-active-text: #f8fbff; |
| | | --menu-surface: linear-gradient(180deg, rgba(13, 43, 79, 0.97) 0%, rgba(8, 28, 52, 0.94) 100%); |
| | | --menu-active-glow: 0 8px 18px rgba(56, 139, 255, 0.28); |
| | | --sidebar-bg: #1e293b; |
| | | --sidebar-text: #94a3b8; |
| | | --sidebar-muted: #64748b; |
| | | --menu-hover: rgba(255, 255, 255, 0.05); |
| | | --menu-active-bg: #3b82f6; |
| | | --menu-active-text: #ffffff; |
| | | --menu-surface: #1e293b; |
| | | --menu-active-glow: 0 4px 12px rgba(59, 130, 246, 0.3); |
| | | |
| | | --app-bg: #f3f7fc; |
| | | --app-bg-accent: #eef5ff; |
| | | --surface-base: rgba(255, 255, 255, 0.92); |
| | | --surface-soft: rgba(255, 255, 255, 0.88); |
| | | --surface-muted: #f5f9ff; |
| | | --surface-border: rgba(148, 163, 184, 0.18); |
| | | --surface-border-strong: rgba(96, 165, 250, 0.34); |
| | | --text-primary: #1e293b; |
| | | --app-bg: #f8fafc; |
| | | --app-bg-accent: #f1f5f9; |
| | | --surface-base: #ffffff; |
| | | --surface-soft: rgba(255, 255, 255, 0.9); |
| | | --surface-muted: #f1f5f9; |
| | | --surface-border: #e2e8f0; |
| | | --surface-border-strong: #cbd5e1; |
| | | --text-primary: #0f172a; |
| | | --text-secondary: #334155; |
| | | --text-tertiary: #64748b; |
| | | --shadow-sm: 0 12px 32px rgba(15, 23, 42, 0.06); |
| | | --shadow-md: 0 20px 42px rgba(15, 23, 42, 0.1); |
| | | --shadow-menu: 0 16px 36px rgba(8, 27, 58, 0.26); |
| | | --radius-lg: 20px; |
| | | --radius-md: 16px; |
| | | --radius-sm: 12px; |
| | | --radius-xs: 10px; |
| | | --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); |
| | | --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); |
| | | --shadow-menu: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); |
| | | --radius-lg: 12px; |
| | | --radius-md: 8px; |
| | | --radius-sm: 6px; |
| | | --radius-xs: 4px; |
| | | |
| | | --navbar-bg: rgba(255, 255, 255, 0.86); |
| | | --navbar-text: #1f3658; |
| | | --navbar-hover: rgba(37, 99, 235, 0.08); |
| | | --navbar-bg: rgba(30, 41, 59, 0.85); |
| | | --navbar-text: #f8fafc; |
| | | --navbar-hover: rgba(255, 255, 255, 0.08); |
| | | |
| | | --tags-bg: transparent; |
| | | --tags-item-bg: rgba(255, 255, 255, 0.9); |
| | | --tags-item-border: rgba(148, 163, 184, 0.22); |
| | | --tags-item-text: #334155; |
| | | --tags-item-hover: #f4f8ff; |
| | | --tags-close-hover: rgba(37, 99, 235, 0.16); |
| | | --tags-bg: #f8fafc; |
| | | --tags-item-bg: #ffffff; |
| | | --tags-item-border: #e2e8f0; |
| | | --tags-item-text: #475569; |
| | | --tags-item-hover: #f1f5f9; |
| | | --tags-close-hover: rgba(239, 68, 68, 0.1); |
| | | |
| | | --accent-primary: #2563eb; |
| | | --accent-primary: #374d77; |
| | | --accent-light: #3b82f6; |
| | | --accent-lighter: #60a5fa; |
| | | |
| | |
| | | .menu-title { |
| | | color: var(--sidebar-text); |
| | | } |
| | | |
| | | |
| | | .el-menu-item.is-active, |
| | | .el-menu-item.is-active .menu-title { |
| | | color: var(--menu-active-text) !important; |
| | |
| | | & .theme-dark .el-sub-menu .el-menu-item { |
| | | background-color: var(--el-bg-color) !important; |
| | | } |
| | | |
| | | |
| | | & .theme-dark .el-sub-menu .el-menu-item.is-active { |
| | | background-color: var(--menu-active-bg) !important; |
| | | } |
| | |
| | | .screenfull-svg {
|
| | | display: inline-block;
|
| | | cursor: pointer;
|
| | | fill: #5a5e66;
|
| | | fill: currentColor;
|
| | | width: 20px;
|
| | | height: 20px;
|
| | | vertical-align: 10px;
|
| | |
| | | </script>
|
| | |
|
| | | <style lang="scss" scoped>
|
| | | .app-main { |
| | | min-height: calc(100vh - var(--topbar-height)); |
| | | width: 100%; |
| | | position: relative; |
| | | overflow: visible; |
| | | background: transparent; |
| | | } |
| | | |
| | | .route-view-wrapper { |
| | | width: 100%; |
| | | height: 100%; |
| | | padding: var(--content-gap); |
| | | padding-top: 0; |
| | | } |
| | | |
| | | .fixed-header + .app-main { |
| | | padding-top: 0; |
| | | } |
| | | |
| | | .hasTagsView { |
| | | .app-main { |
| | | min-height: calc(100vh - var(--topbar-height) - var(--tagsbar-height)); |
| | | } |
| | | .app-main {
|
| | | min-height: calc(100vh - var(--topbar-height));
|
| | | width: 100%;
|
| | | position: relative;
|
| | | overflow: visible;
|
| | | background: transparent;
|
| | | }
|
| | |
|
| | | .route-view-wrapper {
|
| | | width: 100%;
|
| | | height: 100%;
|
| | | padding: var(--content-gap);
|
| | | padding-top: 0;
|
| | | }
|
| | |
|
| | | .fixed-header + .app-main {
|
| | | padding-top: 0;
|
| | | }
|
| | |
|
| | | .hasTagsView {
|
| | | .app-main {
|
| | | min-height: calc(100vh - var(--topbar-height) - var(--tagsbar-height));
|
| | | }
|
| | |
|
| | | .fixed-header + .app-main {
|
| | | padding-top: 0;
|
| | |
| | | <template> |
| | | <div class="navbar"> |
| | | <div class="left-zone"> |
| | | <hamburger |
| | | id="hamburger-container" |
| | | :is-active="appStore.sidebar.opened" |
| | | class="hamburger-container" |
| | | @toggleClick="toggleSideBar" |
| | | /> |
| | | <breadcrumb |
| | | v-if="!settingsStore.topNav" |
| | | id="breadcrumb-container" |
| | | class="breadcrumb-container" |
| | | /> |
| | | <div class="left-menu"> |
| | | <hamburger id="hamburger-container" |
| | | :is-active="appStore.sidebar.opened" |
| | | class="hamburger-container" |
| | | @toggleClick="toggleSideBar" /> |
| | | <breadcrumb v-if="!settingsStore.topNav" |
| | | id="breadcrumb-container" |
| | | class="breadcrumb-container" /> |
| | | </div> |
| | | |
| | | <div class="center-zone"> |
| | | <el-icon class="search-icon" @click="openHeaderSearch"><Search /></el-icon> |
| | | <el-input |
| | | v-model="topSearchKeyword" |
| | | placeholder="æç´¢èå / åè½ / æ°æ®" |
| | | clearable |
| | | @keyup.enter="openHeaderSearch" |
| | | /> |
| | | <header-search |
| | | ref="headerSearchRef" |
| | | :keyword="topSearchKeyword" |
| | | class="search-popup-trigger" |
| | | /> |
| | | </div> |
| | | |
| | | <div class="right-menu"> |
| | | <el-popover |
| | | v-model:visible="notificationVisible" |
| | | :width="500" |
| | | placement="bottom-end" |
| | | trigger="click" |
| | | :popper-options="{ modifiers: [{ name: 'offset', options: { offset: [0, 10] } }] }" |
| | | popper-class="notification-popover" |
| | | > |
| | | <template #reference> |
| | | <div class="notification-container right-menu-item hover-effect"> |
| | | <el-badge :value="unreadCount" :hidden="unreadCount === 0" class="notification-badge"> |
| | | <el-icon :size="18"> |
| | | <Bell /> |
| | | </el-icon> |
| | | </el-badge> |
| | | </div> |
| | | </template> |
| | | <NotificationCenter @unreadCountChange="handleUnreadCountChange" ref="notificationCenterRef" /> |
| | | </el-popover> |
| | | |
| | | <div class="right-menu-item hover-effect screenfull-container"> |
| | | <screenfull /> |
| | | <div class="search-wrapper"> |
| | | <el-icon class="search-icon" |
| | | @click="openHeaderSearch"> |
| | | <Search /> |
| | | </el-icon> |
| | | <el-input v-model="topSearchKeyword" |
| | | placeholder="å¿«éæç´¢..." |
| | | clearable |
| | | @keyup.enter="openHeaderSearch" /> |
| | | <header-search ref="headerSearchRef" |
| | | :keyword="topSearchKeyword" |
| | | class="search-popup-trigger" /> |
| | | </div> |
| | | |
| | | <div class="avatar-container"> |
| | | <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click"> |
| | | <div class="avatar-wrapper"> |
| | | <div class="user-summary"> |
| | | <div class="user-name">{{ userStore.nickName || userStore.name || "管çå" }}</div> |
| | | <div class="user-role">{{ userStore.roleName || "ç³»ç»ç¨æ·" }}</div> |
| | | <div class="action-icons"> |
| | | <el-popover v-model:visible="notificationVisible" |
| | | :width="500" |
| | | placement="bottom-end" |
| | | trigger="click" |
| | | :popper-options="{ modifiers: [{ name: 'offset', options: { offset: [0, 10] } }] }" |
| | | popper-class="notification-popover"> |
| | | <template #reference> |
| | | <div class="notification-container right-menu-item hover-effect"> |
| | | <el-badge :value="unreadCount" |
| | | :hidden="unreadCount === 0" |
| | | class="notification-badge"> |
| | | <el-icon :size="18"> |
| | | <Bell /> |
| | | </el-icon> |
| | | </el-badge> |
| | | </div> |
| | | <img :src="userStore.avatar" class="user-avatar" /> |
| | | <el-icon><caret-bottom /></el-icon> |
| | | </template> |
| | | <NotificationCenter @unreadCountChange="handleUnreadCountChange" |
| | | ref="notificationCenterRef" /> |
| | | </el-popover> |
| | | <div class="right-menu-item hover-effect screenfull-container"> |
| | | <screenfull /> |
| | | </div> |
| | | </div> |
| | | <div class="user-profile"> |
| | | <el-dropdown @command="handleCommand" |
| | | class="profile-dropdown" |
| | | trigger="click"> |
| | | <div class="profile-trigger"> |
| | | <div class="avatar-inner"> |
| | | <img :src="userStore.avatar" |
| | | class="user-avatar" /> |
| | | <div class="user-status-dot"></div> |
| | | </div> |
| | | <div class="profile-trigger-text"> |
| | | <div class="user-name">{{ userStore.nickName || userStore.name || "管çå" }}</div> |
| | | </div> |
| | | <el-icon class="caret-icon"><caret-bottom /></el-icon> |
| | | </div> |
| | | <template #dropdown> |
| | | <el-dropdown-menu> |
| | | <router-link to="/user/profile"> |
| | | <el-dropdown-item>个人ä¸å¿</el-dropdown-item> |
| | | </router-link> |
| | | <el-dropdown-item command="setLayout" v-if="settingsStore.showSettings"> |
| | | <span>å¸å±è®¾ç½®</span> |
| | | </el-dropdown-item> |
| | | <el-dropdown-item divided command="logout"> |
| | | <span>éåºç»å½</span> |
| | | </el-dropdown-item> |
| | | </el-dropdown-menu> |
| | | <div class="user-profile-dropdown-panel"> |
| | | <div class="user-profile-dropdown-head"> |
| | | <img :src="userStore.avatar" |
| | | class="user-profile-dropdown-avatar" /> |
| | | <div class="user-profile-dropdown-meta"> |
| | | <div class="user-profile-dropdown-name">{{ userStore.nickName || userStore.name || "管çå" }}</div> |
| | | <div class="user-profile-dropdown-role">{{ userStore.roleName || "ç³»ç»ç¨æ·" }}</div> |
| | | </div> |
| | | </div> |
| | | <el-dropdown-menu class="user-profile-dropdown"> |
| | | <router-link to="/user/profile"> |
| | | <el-dropdown-item> |
| | | <el-icon> |
| | | <User /> |
| | | </el-icon>个人ä¸å¿ |
| | | </el-dropdown-item> |
| | | </router-link> |
| | | <el-dropdown-item command="setLayout" |
| | | v-if="settingsStore.showSettings"> |
| | | <el-icon> |
| | | <Setting /> |
| | | </el-icon>å¸å±è®¾ç½® |
| | | </el-dropdown-item> |
| | | <el-dropdown-item divided |
| | | command="logout" |
| | | class="logout-item"> |
| | | <el-icon> |
| | | <SwitchButton /> |
| | | </el-icon>éåºç»å½ |
| | | </el-dropdown-item> |
| | | </el-dropdown-menu> |
| | | </div> |
| | | </template> |
| | | </el-dropdown> |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { Bell, Search } from "@element-plus/icons-vue"; |
| | | import Breadcrumb from "@/components/Breadcrumb"; |
| | | import Hamburger from "@/components/Hamburger"; |
| | | import Screenfull from "@/components/Screenfull"; |
| | | import HeaderSearch from "@/components/HeaderSearch"; |
| | | import NotificationCenter from "./NotificationCenter/index.vue"; |
| | | import useAppStore from "@/store/modules/app"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import useSettingsStore from "@/store/modules/settings"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { Bell, Search } from "@element-plus/icons-vue"; |
| | | import Breadcrumb from "@/components/Breadcrumb"; |
| | | import Hamburger from "@/components/Hamburger"; |
| | | import Screenfull from "@/components/Screenfull"; |
| | | import HeaderSearch from "@/components/HeaderSearch"; |
| | | import NotificationCenter from "./NotificationCenter/index.vue"; |
| | | import useAppStore from "@/store/modules/app"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import useSettingsStore from "@/store/modules/settings"; |
| | | |
| | | const appStore = useAppStore(); |
| | | const userStore = useUserStore(); |
| | | const settingsStore = useSettingsStore(); |
| | | const appStore = useAppStore(); |
| | | const userStore = useUserStore(); |
| | | const settingsStore = useSettingsStore(); |
| | | |
| | | const topSearchKeyword = ref(""); |
| | | const headerSearchRef = ref(null); |
| | | const notificationVisible = ref(false); |
| | | const notificationCenterRef = ref(null); |
| | | const unreadCount = ref(0); |
| | | const topSearchKeyword = ref(""); |
| | | const headerSearchRef = ref(null); |
| | | const notificationVisible = ref(false); |
| | | const notificationCenterRef = ref(null); |
| | | const unreadCount = ref(0); |
| | | |
| | | function toggleSideBar() { |
| | | appStore.toggleSideBar(); |
| | | } |
| | | |
| | | function openHeaderSearch() { |
| | | headerSearchRef.value?.open(topSearchKeyword.value); |
| | | } |
| | | |
| | | function handleCommand(command) { |
| | | switch (command) { |
| | | case "setLayout": |
| | | setLayout(); |
| | | break; |
| | | case "logout": |
| | | logout(); |
| | | break; |
| | | default: |
| | | break; |
| | | function toggleSideBar() { |
| | | appStore.toggleSideBar(); |
| | | } |
| | | } |
| | | |
| | | function logout() { |
| | | ElMessageBox.confirm("ç¡®å®æ³¨éå¹¶éåºç³»ç»åï¼", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | userStore.logOut().then(() => { |
| | | location.href = "/index"; |
| | | }); |
| | | function openHeaderSearch() { |
| | | headerSearchRef.value?.open(topSearchKeyword.value); |
| | | } |
| | | |
| | | function handleCommand(command) { |
| | | switch (command) { |
| | | case "setLayout": |
| | | setLayout(); |
| | | break; |
| | | case "logout": |
| | | logout(); |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | } |
| | | |
| | | function logout() { |
| | | ElMessageBox.confirm("ç¡®å®æ³¨éå¹¶éåºç³»ç»åï¼", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .catch(() => {}); |
| | | } |
| | | .then(() => { |
| | | userStore.logOut().then(() => { |
| | | location.href = "/index"; |
| | | }); |
| | | }) |
| | | .catch(() => {}); |
| | | } |
| | | |
| | | const emits = defineEmits(["setLayout"]); |
| | | function setLayout() { |
| | | emits("setLayout"); |
| | | } |
| | | const emits = defineEmits(["setLayout"]); |
| | | function setLayout() { |
| | | emits("setLayout"); |
| | | } |
| | | |
| | | function handleUnreadCountChange(count) { |
| | | unreadCount.value = count; |
| | | } |
| | | function handleUnreadCountChange(count) { |
| | | unreadCount.value = count; |
| | | } |
| | | |
| | | let unreadCountTimer = null; |
| | | onMounted(() => { |
| | | nextTick(() => { |
| | | if (notificationCenterRef.value) { |
| | | notificationCenterRef.value.loadUnreadCount(); |
| | | let unreadCountTimer = null; |
| | | onMounted(() => { |
| | | nextTick(() => { |
| | | if (notificationCenterRef.value) { |
| | | notificationCenterRef.value.loadUnreadCount(); |
| | | } |
| | | }); |
| | | |
| | | unreadCountTimer = setInterval(() => { |
| | | if (notificationCenterRef.value) { |
| | | notificationCenterRef.value.loadUnreadCount(); |
| | | } |
| | | }, 30000); |
| | | }); |
| | | |
| | | watch(notificationVisible, val => { |
| | | if (val && notificationCenterRef.value) { |
| | | nextTick(() => { |
| | | notificationCenterRef.value.loadMessages(); |
| | | }); |
| | | } |
| | | }); |
| | | |
| | | unreadCountTimer = setInterval(() => { |
| | | if (notificationCenterRef.value) { |
| | | notificationCenterRef.value.loadUnreadCount(); |
| | | onUnmounted(() => { |
| | | if (unreadCountTimer) { |
| | | clearInterval(unreadCountTimer); |
| | | } |
| | | }, 30000); |
| | | }); |
| | | |
| | | watch(notificationVisible, (val) => { |
| | | if (val && notificationCenterRef.value) { |
| | | nextTick(() => { |
| | | notificationCenterRef.value.loadMessages(); |
| | | }); |
| | | } |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | | if (unreadCountTimer) { |
| | | clearInterval(unreadCountTimer); |
| | | } |
| | | }); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .navbar { |
| | | height: var(--topbar-height); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | gap: 14px; |
| | | background: rgba(255, 255, 255, 0.86); |
| | | border: 1px solid rgba(148, 163, 184, 0.18); |
| | | border-radius: var(--content-radius); |
| | | backdrop-filter: blur(16px); |
| | | box-shadow: 0 8px 20px rgba(15, 23, 42, 0.05); |
| | | padding: 0 18px; |
| | | } |
| | | |
| | | .left-zone { |
| | | flex: 0 1 420px; |
| | | min-width: 0; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .hamburger-container { |
| | | line-height: 36px; |
| | | height: 36px; |
| | | width: 36px; |
| | | border-radius: 10px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: var(--navbar-text); |
| | | |
| | | &:hover { |
| | | background: var(--navbar-hover); |
| | | } |
| | | } |
| | | |
| | | .breadcrumb-container { |
| | | min-width: 0; |
| | | } |
| | | |
| | | .center-zone { |
| | | width: clamp(360px, 34vw, 560px); |
| | | min-width: 320px; |
| | | height: 38px; |
| | | border-radius: 999px; |
| | | border: 1px solid rgba(148, 163, 184, 0.24); |
| | | background: rgba(248, 251, 255, 0.92); |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 0 12px; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .search-icon { |
| | | color: #5b86c9; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .center-zone :deep(.el-input__wrapper) { |
| | | border: 0; |
| | | box-shadow: none !important; |
| | | background: transparent; |
| | | padding: 0; |
| | | } |
| | | |
| | | .center-zone :deep(.el-input__inner) { |
| | | color: #334155; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .search-popup-trigger :deep(.search-icon) { |
| | | color: #5b86c9; |
| | | font-size: 16px; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .right-menu { |
| | | height: 100%; |
| | | align-items: center; |
| | | display: flex; |
| | | gap: 14px; |
| | | } |
| | | |
| | | .right-menu-item { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: var(--navbar-text); |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .hover-effect { |
| | | cursor: pointer; |
| | | transition: background 0.2s; |
| | | |
| | | &:hover { |
| | | background: var(--navbar-hover); |
| | | } |
| | | } |
| | | |
| | | .notification-container, |
| | | .screenfull-container { |
| | | width: 36px; |
| | | height: 36px; |
| | | } |
| | | |
| | | .notification-badge :deep(.el-badge__content) { |
| | | border: none; |
| | | } |
| | | |
| | | .screenfull-container :deep(.svg-icon) { |
| | | width: 16px; |
| | | height: 16px; |
| | | color: var(--navbar-text); |
| | | } |
| | | |
| | | .avatar-container { |
| | | height: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .avatar-container :deep(.el-dropdown) { |
| | | height: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .avatar-wrapper { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | padding: 4px 10px 4px 8px; |
| | | height: 44px; |
| | | border-radius: 22px; |
| | | background: rgba(255, 255, 255, 0.9); |
| | | border: 1px solid rgba(148, 163, 184, 0.22); |
| | | } |
| | | |
| | | .user-summary { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: flex-end; |
| | | gap: 2px; |
| | | } |
| | | |
| | | .user-name { |
| | | color: var(--text-primary); |
| | | font-size: 13px; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .user-role { |
| | | color: var(--text-tertiary); |
| | | font-size: 11px; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .user-avatar { |
| | | cursor: pointer; |
| | | width: 36px; |
| | | height: 36px; |
| | | border-radius: 50%; |
| | | border: 1px solid rgba(148, 163, 184, 0.3); |
| | | } |
| | | |
| | | @media (max-width: 1200px) { |
| | | .center-zone { |
| | | display: none; |
| | | .navbar { |
| | | height: var(--topbar-height); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 0 24px; |
| | | background: var(--navbar-bg); |
| | | border-bottom: 1px solid rgba(255, 255, 255, 0.08); |
| | | backdrop-filter: blur(12px); |
| | | z-index: var(--layout-header-z); |
| | | } |
| | | |
| | | .user-summary { |
| | | display: none; |
| | | .left-menu { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | } |
| | | } |
| | | |
| | | .hamburger-container { |
| | | height: 32px; |
| | | width: 32px; |
| | | border-radius: var(--radius-sm); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: var(--navbar-text); |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | |
| | | &:hover { |
| | | background: var(--navbar-hover); |
| | | color: #fff; |
| | | } |
| | | } |
| | | |
| | | .breadcrumb-container { |
| | | min-width: 0; |
| | | |
| | | :deep(.el-breadcrumb__inner) { |
| | | color: var(--navbar-text) !important; |
| | | opacity: 0.85; |
| | | |
| | | &:hover { |
| | | color: #fff !important; |
| | | opacity: 1; |
| | | } |
| | | |
| | | a { |
| | | color: inherit !important; |
| | | font-weight: 500 !important; |
| | | } |
| | | } |
| | | |
| | | :deep(.no-redirect) { |
| | | color: #fff !important; |
| | | font-weight: 600 !important; |
| | | opacity: 1; |
| | | } |
| | | |
| | | :deep(.el-breadcrumb__separator) { |
| | | color: var(--navbar-text); |
| | | opacity: 0.5; |
| | | } |
| | | } |
| | | |
| | | .right-menu { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 20px; // å¢å 大ç»ä¹é´çé´è· |
| | | |
| | | .search-wrapper { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | height: 34px; |
| | | padding: 0 12px; |
| | | background: var(--navbar-hover); |
| | | border: 1px solid var(--surface-border); |
| | | border-radius: 17px; |
| | | width: 240px; // æç´¢æ¡æ´å ç²¾è´å°å·§ |
| | | transition: all 0.3s ease; |
| | | |
| | | &:focus-within { |
| | | width: 300px; |
| | | background: rgba(255, 255, 255, 0.1); |
| | | border-color: var(--accent-primary); |
| | | box-shadow: 0 0 0 2px rgba(var(--el-color-primary-rgb), 0.2); |
| | | } |
| | | |
| | | .search-icon { |
| | | color: var(--sidebar-text); |
| | | font-size: 16px; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | :deep(.el-input__wrapper) { |
| | | background: transparent; |
| | | box-shadow: none !important; |
| | | padding: 0; |
| | | } |
| | | |
| | | :deep(.el-input__inner) { |
| | | color: var(--navbar-text); |
| | | font-size: 13px; |
| | | height: 32px; |
| | | |
| | | &::placeholder { |
| | | color: var(--sidebar-text); |
| | | } |
| | | } |
| | | } |
| | | |
| | | .action-icons { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | padding-right: 16px; |
| | | border-right: 1px solid var(--surface-border); // å¢å åç´åå²çº¿ |
| | | |
| | | .right-menu-item { |
| | | padding: 0 8px; |
| | | height: 34px; |
| | | display: flex; |
| | | align-items: center; |
| | | color: var(--navbar-text); |
| | | border-radius: var(--radius-sm); |
| | | transition: all 0.2s ease; |
| | | cursor: pointer; |
| | | |
| | | &:hover { |
| | | background: var(--navbar-hover); |
| | | color: var(--menu-active-text); |
| | | } |
| | | } |
| | | } |
| | | |
| | | .user-profile { |
| | | .profile-dropdown { |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .profile-trigger { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | height: 38px; |
| | | padding: 4px 10px 4px 6px; |
| | | border-radius: 999px; |
| | | border: 1px solid rgba(var(--el-color-primary-rgb), 0.32); |
| | | background: rgba(0, 0, 0, 0.16); |
| | | transition: 0.2s ease; |
| | | |
| | | &:hover { |
| | | background: rgba(0, 0, 0, 0.24); |
| | | border-color: rgba(var(--el-color-primary-rgb), 0.58); |
| | | box-shadow: 0 0 0 2px rgba(var(--el-color-primary-rgb), 0.18); |
| | | } |
| | | } |
| | | |
| | | .profile-trigger-text { |
| | | display: flex; |
| | | align-items: center; |
| | | max-width: 120px; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .user-name { |
| | | font-size: 13px; |
| | | font-weight: 700; |
| | | color: var(--navbar-text); |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .avatar-inner { |
| | | position: relative; |
| | | flex-shrink: 0; |
| | | display: flex; |
| | | } |
| | | |
| | | .user-avatar { |
| | | width: 30px; |
| | | height: 30px; |
| | | border-radius: 999px; |
| | | border: 2px solid rgba(var(--el-color-primary-rgb), 0.28); |
| | | box-shadow: 0 8px 18px rgba(0, 0, 0, 0.18); |
| | | object-fit: cover; |
| | | transition: 0.2s ease; |
| | | } |
| | | |
| | | .profile-trigger:hover .user-avatar { |
| | | transform: translateY(-1px); |
| | | } |
| | | |
| | | .user-status-dot { |
| | | position: absolute; |
| | | bottom: -1px; |
| | | right: -1px; |
| | | width: 9px; |
| | | height: 9px; |
| | | background: #10b981; |
| | | border: 2px solid var(--navbar-bg); |
| | | border-radius: 999px; |
| | | } |
| | | |
| | | .caret-icon { |
| | | color: var(--navbar-text); |
| | | opacity: 0.76; |
| | | font-size: 12px; |
| | | transition: 0.2s ease; |
| | | margin-left: 2px; |
| | | } |
| | | |
| | | .profile-trigger:hover .caret-icon { |
| | | transform: translateY(1px); |
| | | opacity: 1; |
| | | } |
| | | } |
| | | } |
| | | |
| | | :deep(.user-profile-dropdown-panel) { |
| | | min-width: 240px; |
| | | color: var(--sidebar-text); |
| | | } |
| | | |
| | | :deep(.user-profile-dropdown-head) { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | padding: 12px 12px 10px; |
| | | margin: 0 6px 6px; |
| | | border-radius: var(--radius-lg); |
| | | // background: rgba(0, 0, 0, 0.18); |
| | | // border: 1px solid rgba(var(--el-color-primary-rgb), 0.22); |
| | | } |
| | | |
| | | :deep(.user-profile-dropdown-avatar) { |
| | | width: 38px; |
| | | height: 38px; |
| | | border-radius: 999px; |
| | | border: 2px solid rgba(var(--el-color-primary-rgb), 0.26); |
| | | object-fit: cover; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | :deep(.user-profile-dropdown-meta) { |
| | | min-width: 0; |
| | | } |
| | | |
| | | :deep(.user-profile-dropdown-name) { |
| | | font-size: 13px; |
| | | font-weight: 800; |
| | | color: #000; |
| | | line-height: 1.2; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | :deep(.user-profile-dropdown-role) { |
| | | margin-top: 2px; |
| | | font-size: 12px; |
| | | // color: var(--sidebar-muted, rgba(255, 255, 255, 0.62)); |
| | | color: #64748b; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | :deep(.user-profile-dropdown) { |
| | | background: var(--sidebar-bg) !important; |
| | | border: 1px solid var(--surface-border) !important; |
| | | border-radius: var(--radius-lg) !important; |
| | | box-shadow: var(--shadow-menu) !important; |
| | | padding: 6px !important; |
| | | backdrop-filter: blur(16px); |
| | | |
| | | .el-dropdown-menu__item { |
| | | color: var(--sidebar-text) !important; |
| | | border-radius: var(--radius-md) !important; |
| | | margin: 2px 0 !important; |
| | | padding: 8px 16px !important; |
| | | font-size: 13px !important; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | |
| | | .el-icon { |
| | | font-size: 16px; |
| | | margin-right: 0; |
| | | } |
| | | |
| | | &:hover { |
| | | background: var(--menu-hover) !important; |
| | | color: var(--menu-active-text) !important; |
| | | } |
| | | |
| | | &.el-dropdown-menu__item--divided { |
| | | border-top-color: var(--surface-border) !important; |
| | | margin-top: 6px !important; |
| | | padding-top: 10px !important; |
| | | |
| | | &::before { |
| | | display: none; |
| | | } |
| | | } |
| | | |
| | | &.logout-item { |
| | | color: #f87171 !important; |
| | | |
| | | &:hover { |
| | | background: rgba(239, 68, 68, 0.1) !important; |
| | | color: #ef4444 !important; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | | |
| | | <style lang="scss"> |
| | | .notification-popover { |
| | | padding: 0 !important; |
| | | border-radius: 16px !important; |
| | | border: 1px solid rgba(148, 163, 184, 0.22) !important; |
| | | box-shadow: 0 18px 40px rgba(15, 23, 42, 0.12) !important; |
| | | background: rgba(255, 255, 255, 0.94) !important; |
| | | backdrop-filter: blur(16px); |
| | | |
| | | .el-popover__title { |
| | | display: none; |
| | | } |
| | | |
| | | .el-popover__body { |
| | | .notification-popover { |
| | | padding: 0 !important; |
| | | } |
| | | } |
| | | border-radius: var(--radius-lg) !important; |
| | | border: 1px solid rgba(15, 23, 42, 0.1) !important; |
| | | box-shadow: var(--shadow-menu) !important; |
| | | background: rgba(255, 255, 255, 0.96) !important; |
| | | backdrop-filter: blur(16px); |
| | | color: rgba(15, 23, 42, 0.92); |
| | | |
| | | .el-badge__content.is-fixed { |
| | | top: 8px; |
| | | } |
| | | .el-popover__title { |
| | | display: none; |
| | | } |
| | | |
| | | .el-popover__body { |
| | | padding: 0 !important; |
| | | } |
| | | } |
| | | |
| | | .el-badge__content.is-fixed { |
| | | top: 8px; |
| | | } |
| | | </style> |
| | |
| | | flex-direction: column; |
| | | width: 500px; |
| | | padding: 16px; |
| | | background: rgba(255, 255, 255, 0.92); |
| | | background: transparent; |
| | | color: rgba(15, 23, 42, 0.92); |
| | | } |
| | | |
| | | .popover-header { |
| | |
| | | width: 100%; |
| | | margin-bottom: 16px; |
| | | padding-bottom: 12px; |
| | | border-bottom: 1px solid var(--surface-border); |
| | | border-bottom: 1px solid rgba(15, 23, 42, 0.08); |
| | | |
| | | .popover-title { |
| | | font-size: 18px; |
| | | font-weight: 500; |
| | | color: var(--text-primary); |
| | | color: rgba(15, 23, 42, 0.92); |
| | | } |
| | | } |
| | | |
| | |
| | | flex-direction: column; |
| | | min-height: 0; |
| | | |
| | | .el-tabs__nav-wrap::after { |
| | | background-color: rgba(15, 23, 42, 0.08); |
| | | } |
| | | |
| | | .el-tabs__item { |
| | | color: rgba(15, 23, 42, 0.7); |
| | | |
| | | &.is-active { |
| | | color: var(--accent-primary); |
| | | } |
| | | } |
| | | |
| | | .el-tabs__header { |
| | | margin-bottom: 0; |
| | | flex-shrink: 0; |
| | |
| | | min-height: 0; |
| | | padding-top: 16px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .el-tab-pane { |
| | | height: 100%; |
| | | .notification-list { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .notification-item { |
| | | display: flex; |
| | | gap: 12px; |
| | | padding: 12px; |
| | | border-radius: var(--radius-md); |
| | | background: rgba(15, 23, 42, 0.03); |
| | | border: 1px solid rgba(15, 23, 42, 0.06); |
| | | transition: all 0.2s ease; |
| | | |
| | | &:hover { |
| | | background: rgba(15, 23, 42, 0.05); |
| | | border-color: rgba(15, 23, 42, 0.1); |
| | | } |
| | | |
| | | &.read { |
| | | opacity: 0.6; |
| | | } |
| | | |
| | | .notification-icon { |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .notification-content-wrapper { |
| | | flex: 1; |
| | | min-width: 0; |
| | | |
| | | .notification-title { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: rgba(15, 23, 42, 0.92); |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .notification-detail { |
| | | font-size: 13px; |
| | | color: rgba(15, 23, 42, 0.7); |
| | | line-height: 1.4; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .notification-time { |
| | | font-size: 11px; |
| | | color: rgba(15, 23, 42, 0.55); |
| | | } |
| | | } |
| | | } |
| | | |
| | | .empty-state { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | min-height: 300px; |
| | | padding: 40px 0; |
| | | } |
| | | |
| | | .notification-list { |
| | | .notification-item { |
| | | display: flex; |
| | | padding: 12px 0; |
| | | border-bottom: 1px solid rgba(148, 163, 184, 0.18); |
| | | transition: background-color 0.3s; |
| | | |
| | | &:hover { |
| | | background-color: #f8fbff; |
| | | } |
| | | |
| | | &.read { |
| | | opacity: 0.7; |
| | | } |
| | | |
| | | .notification-icon { |
| | | flex-shrink: 0; |
| | | width: 40px; |
| | | height: 40px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background-color: rgba(59, 130, 246, 0.12); |
| | | border-radius: 50%; |
| | | margin-right: 12px; |
| | | } |
| | | |
| | | .notification-content-wrapper { |
| | | flex: 1; |
| | | min-width: 0; |
| | | |
| | | .notification-title { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: var(--text-primary); |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .notification-detail { |
| | | font-size: 13px; |
| | | color: var(--text-secondary); |
| | | line-height: 1.5; |
| | | margin-bottom: 8px; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .notification-time { |
| | | font-size: 12px; |
| | | color: var(--text-tertiary); |
| | | } |
| | | } |
| | | |
| | | .notification-action { |
| | | flex-shrink: 0; |
| | | margin-left: 12px; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | :deep(.el-empty__description p) { |
| | | color: rgba(15, 23, 42, 0.68); |
| | | } |
| | | } |
| | | |
| | | .pagination-wrapper { |
| | | margin-top: 16px; |
| | | padding-top: 16px; |
| | | border-top: 1px solid var(--surface-border); |
| | | border-top: 1px solid rgba(15, 23, 42, 0.08); |
| | | display: flex; |
| | | justify-content: center; |
| | | padding-left: 0; |
| | | padding-right: 0; |
| | | justify-content: flex-end; |
| | | |
| | | :deep(.el-pagination) { |
| | | --el-pagination-bg-color: transparent; |
| | | --el-pagination-button-bg-color: transparent; |
| | | --el-pagination-hover-color: var(--accent-primary); |
| | | |
| | | button, |
| | | .el-pager li { |
| | | color: rgba(15, 23, 42, 0.7); |
| | | background: transparent; |
| | | |
| | | &:disabled { |
| | | color: rgba(15, 23, 42, 0.3); |
| | | } |
| | | } |
| | | |
| | | .el-pager li.is-active { |
| | | color: var(--accent-primary); |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | | |
| | |
| | | <template>
|
| | | <el-drawer v-model="showSettings" direction="rtl" size="300px">
|
| | | <!-- <div class="setting-drawer-title">-->
|
| | | <!-- <h3 class="drawer-title">主é¢é£æ ¼è®¾ç½®</h3>-->
|
| | | <!-- </div>-->
|
| | | <!-- <div class="setting-drawer-block-checbox">-->
|
| | | <!-- <div-->
|
| | | <!-- class="setting-drawer-block-checbox-item"-->
|
| | | <!-- @click="handleTheme('theme-dark')"-->
|
| | | <!-- >-->
|
| | | <!-- <img src="@/assets/images/dark.svg" alt="dark" />-->
|
| | | <!-- <div-->
|
| | | <!-- v-if="sideTheme === 'theme-dark'"-->
|
| | | <!-- class="setting-drawer-block-checbox-selectIcon"-->
|
| | | <!-- style="display: block"-->
|
| | | <!-- >-->
|
| | | <!-- <i aria-label="徿 : check" class="anticon anticon-check">-->
|
| | | <!-- <svg-->
|
| | | <!-- viewBox="64 64 896 896"-->
|
| | | <!-- data-icon="check"-->
|
| | | <!-- width="1em"-->
|
| | | <!-- height="1em"-->
|
| | | <!-- :fill="theme"-->
|
| | | <!-- aria-hidden="true"-->
|
| | | <!-- focusable="false"-->
|
| | | <!-- class-->
|
| | | <!-- >-->
|
| | | <!-- <path-->
|
| | | <!-- d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"-->
|
| | | <!-- />-->
|
| | | <!-- </svg>-->
|
| | | <!-- </i>-->
|
| | | <!-- </div>-->
|
| | | <!-- </div>-->
|
| | | <!-- <div-->
|
| | | <!-- class="setting-drawer-block-checbox-item"-->
|
| | | <!-- @click="handleTheme('theme-light')"-->
|
| | | <!-- >-->
|
| | | <!-- <img src="@/assets/images/light.svg" alt="light" />-->
|
| | | <!-- <div-->
|
| | | <!-- v-if="sideTheme === 'theme-light'"-->
|
| | | <!-- class="setting-drawer-block-checbox-selectIcon"-->
|
| | | <!-- style="display: block"-->
|
| | | <!-- >-->
|
| | | <!-- <i aria-label="徿 : check" class="anticon anticon-check">-->
|
| | | <!-- <svg-->
|
| | | <!-- viewBox="64 64 896 896"-->
|
| | | <!-- data-icon="check"-->
|
| | | <!-- width="1em"-->
|
| | | <!-- height="1em"-->
|
| | | <!-- :fill="theme"-->
|
| | | <!-- aria-hidden="true"-->
|
| | | <!-- focusable="false"-->
|
| | | <!-- class-->
|
| | | <!-- >-->
|
| | | <!-- <path-->
|
| | | <!-- d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"-->
|
| | | <!-- />-->
|
| | | <!-- </svg>-->
|
| | | <!-- </i>-->
|
| | | <!-- </div>-->
|
| | | <!-- </div>-->
|
| | | <!-- </div>-->
|
| | | <el-drawer v-model="showSettings"
|
| | | direction="rtl"
|
| | | size="300px">
|
| | | <!-- <div class="setting-drawer-title">-->
|
| | | <!-- <h3 class="drawer-title">主é¢é£æ ¼è®¾ç½®</h3>-->
|
| | | <!-- </div>-->
|
| | | <!-- <div class="setting-drawer-block-checbox">-->
|
| | | <!-- <div-->
|
| | | <!-- class="setting-drawer-block-checbox-item"-->
|
| | | <!-- @click="handleTheme('theme-dark')"-->
|
| | | <!-- >-->
|
| | | <!-- <img src="@/assets/images/dark.svg" alt="dark" />-->
|
| | | <!-- <div-->
|
| | | <!-- v-if="sideTheme === 'theme-dark'"-->
|
| | | <!-- class="setting-drawer-block-checbox-selectIcon"-->
|
| | | <!-- style="display: block"-->
|
| | | <!-- >-->
|
| | | <!-- <i aria-label="徿 : check" class="anticon anticon-check">-->
|
| | | <!-- <svg-->
|
| | | <!-- viewBox="64 64 896 896"-->
|
| | | <!-- data-icon="check"-->
|
| | | <!-- width="1em"-->
|
| | | <!-- height="1em"-->
|
| | | <!-- :fill="theme"-->
|
| | | <!-- aria-hidden="true"-->
|
| | | <!-- focusable="false"-->
|
| | | <!-- class-->
|
| | | <!-- >-->
|
| | | <!-- <path-->
|
| | | <!-- d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"-->
|
| | | <!-- />-->
|
| | | <!-- </svg>-->
|
| | | <!-- </i>-->
|
| | | <!-- </div>-->
|
| | | <!-- </div>-->
|
| | | <!-- <div-->
|
| | | <!-- class="setting-drawer-block-checbox-item"-->
|
| | | <!-- @click="handleTheme('theme-light')"-->
|
| | | <!-- >-->
|
| | | <!-- <img src="@/assets/images/light.svg" alt="light" />-->
|
| | | <!-- <div-->
|
| | | <!-- v-if="sideTheme === 'theme-light'"-->
|
| | | <!-- class="setting-drawer-block-checbox-selectIcon"-->
|
| | | <!-- style="display: block"-->
|
| | | <!-- >-->
|
| | | <!-- <i aria-label="徿 : check" class="anticon anticon-check">-->
|
| | | <!-- <svg-->
|
| | | <!-- viewBox="64 64 896 896"-->
|
| | | <!-- data-icon="check"-->
|
| | | <!-- width="1em"-->
|
| | | <!-- height="1em"-->
|
| | | <!-- :fill="theme"-->
|
| | | <!-- aria-hidden="true"-->
|
| | | <!-- focusable="false"-->
|
| | | <!-- class-->
|
| | | <!-- >-->
|
| | | <!-- <path-->
|
| | | <!-- d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"-->
|
| | | <!-- />-->
|
| | | <!-- </svg>-->
|
| | | <!-- </i>-->
|
| | | <!-- </div>-->
|
| | | <!-- </div>-->
|
| | | <!-- </div>-->
|
| | | <div class="drawer-item">
|
| | | <span>主é¢é¢è²</span>
|
| | | <span class="comp-style">
|
| | | <el-color-picker
|
| | | v-model="theme"
|
| | | :predefine="predefineColors"
|
| | | @change="themeChange"
|
| | | />
|
| | | <el-color-picker v-model="theme"
|
| | | :predefine="predefineColors"
|
| | | @change="themeChange" />
|
| | | </span>
|
| | | </div>
|
| | | <div class="drawer-item">
|
| | | <span>æ¾ç¤ºæ¨¡å¼</span>
|
| | | <span class="comp-style">
|
| | | <el-select
|
| | | v-model="settingsStore.darkMode"
|
| | | placeholder="è¯·éæ©"
|
| | | style="width: 130px"
|
| | | @change="darkModeChange"
|
| | | >
|
| | | <el-option
|
| | | v-for="item in darkModeOptions"
|
| | | :key="item.value"
|
| | | :label="item.label"
|
| | | :value="item.value"
|
| | | />
|
| | | <el-select v-model="settingsStore.darkMode"
|
| | | placeholder="è¯·éæ©"
|
| | | style="width: 130px"
|
| | | @change="darkModeChange">
|
| | | <el-option v-for="item in darkModeOptions"
|
| | | :key="item.value"
|
| | | :label="item.label"
|
| | | :value="item.value" />
|
| | | </el-select>
|
| | | </span>
|
| | | </div>
|
| | | <el-divider />
|
| | |
|
| | | <h3 class="drawer-title">ç³»ç»å¸å±é
ç½®</h3>
|
| | |
|
| | | <div class="drawer-item">
|
| | | <span>å¼å¯ TopNav</span>
|
| | | <span class="comp-style">
|
| | | <el-switch
|
| | | v-model="settingsStore.topNav"
|
| | | @change="topNavChange"
|
| | | class="drawer-switch"
|
| | | />
|
| | | <el-switch v-model="settingsStore.topNav"
|
| | | @change="topNavChange"
|
| | | class="drawer-switch" />
|
| | | </span>
|
| | | </div>
|
| | |
|
| | | <div class="drawer-item">
|
| | | <span>å¼å¯ Tags-Views</span>
|
| | | <span class="comp-style">
|
| | | <el-switch v-model="settingsStore.tagsView" class="drawer-switch" />
|
| | | <el-switch v-model="settingsStore.tagsView"
|
| | | class="drawer-switch" />
|
| | | </span>
|
| | | </div>
|
| | |
|
| | | <div class="drawer-item">
|
| | | <span>åºå® Header</span>
|
| | | <span class="comp-style">
|
| | | <el-switch v-model="settingsStore.fixedHeader" class="drawer-switch" />
|
| | | <el-switch v-model="settingsStore.fixedHeader"
|
| | | class="drawer-switch" />
|
| | | </span>
|
| | | </div>
|
| | |
|
| | | <div class="drawer-item">
|
| | | <span>æ¾ç¤º Logo</span>
|
| | | <span class="comp-style">
|
| | | <el-switch v-model="settingsStore.sidebarLogo" class="drawer-switch" />
|
| | | <el-switch v-model="settingsStore.sidebarLogo"
|
| | | class="drawer-switch" />
|
| | | </span>
|
| | | </div>
|
| | |
|
| | | <div class="drawer-item">
|
| | | <span>卿æ é¢</span>
|
| | | <span class="comp-style">
|
| | | <el-switch v-model="settingsStore.dynamicTitle" class="drawer-switch" />
|
| | | <el-switch v-model="settingsStore.dynamicTitle"
|
| | | class="drawer-switch" />
|
| | | </span>
|
| | | </div>
|
| | |
|
| | | <el-divider />
|
| | |
|
| | | <el-button type="primary" plain icon="DocumentAdd" @click="saveSetting"
|
| | | >ä¿åé
ç½®</el-button
|
| | | >
|
| | | <el-button plain icon="Refresh" @click="resetSetting">éç½®é
ç½®</el-button>
|
| | | <el-button type="primary"
|
| | | plain
|
| | | icon="DocumentAdd"
|
| | | @click="saveSetting">ä¿åé
ç½®</el-button>
|
| | | <el-button plain
|
| | | icon="Refresh"
|
| | | @click="resetSetting">éç½®é
ç½®</el-button>
|
| | | </el-drawer>
|
| | | </template>
|
| | |
|
| | | <script setup>
|
| | | import variables from "@/assets/styles/variables.module.scss";
|
| | | import axios from "axios";
|
| | | import { ElLoading, ElMessage } from "element-plus";
|
| | | import { useDynamicTitle } from "@/utils/dynamicTitle";
|
| | | import useAppStore from "@/store/modules/app";
|
| | | import useSettingsStore from "@/store/modules/settings";
|
| | | import usePermissionStore from "@/store/modules/permission";
|
| | | import { handleThemeStyle } from "@/utils/theme";
|
| | | import variables from "@/assets/styles/variables.module.scss";
|
| | | import axios from "axios";
|
| | | import { ElLoading, ElMessage } from "element-plus";
|
| | | import { useDynamicTitle } from "@/utils/dynamicTitle";
|
| | | import useAppStore from "@/store/modules/app";
|
| | | import useSettingsStore from "@/store/modules/settings";
|
| | | import usePermissionStore from "@/store/modules/permission";
|
| | | import { handleThemeStyle } from "@/utils/theme";
|
| | |
|
| | | const { proxy } = getCurrentInstance();
|
| | | const appStore = useAppStore();
|
| | | const settingsStore = useSettingsStore();
|
| | | const permissionStore = usePermissionStore();
|
| | | const showSettings = ref(false);
|
| | | const theme = ref(settingsStore.theme);
|
| | | const sideTheme = ref(settingsStore.sideTheme);
|
| | | const storeSettings = computed(() => settingsStore);
|
| | | const predefineColors = ref([
|
| | | "#002fa7",
|
| | | "#81D8D0",
|
| | | "#E85827",
|
| | | "#008C8C",
|
| | | "#F9DC24",
|
| | | "#B05923",
|
| | | "#003153",
|
| | | "#8F4B28",
|
| | | "#4C0009",
|
| | | ]);
|
| | | const darkModeOptions = ref([
|
| | | { label: "è·éç³»ç»", value: "auto" },
|
| | | { label: "æµ
è²", value: "light" },
|
| | | { label: "æ·±è²", value: "dark" },
|
| | | ]);
|
| | | const { proxy } = getCurrentInstance();
|
| | | const appStore = useAppStore();
|
| | | const settingsStore = useSettingsStore();
|
| | | const permissionStore = usePermissionStore();
|
| | | const showSettings = ref(false);
|
| | | const theme = ref(settingsStore.theme);
|
| | | const sideTheme = ref(settingsStore.sideTheme);
|
| | | const storeSettings = computed(() => settingsStore);
|
| | | const predefineColors = ref([
|
| | | "#374D77",
|
| | | "#81D8D0",
|
| | | "#E85827",
|
| | | "#008C8C",
|
| | | "#F9DC24",
|
| | | "#B05923",
|
| | | "#003153",
|
| | | "#8F4B28",
|
| | | "#4C0009",
|
| | | ]);
|
| | | const darkModeOptions = ref([
|
| | | { label: "è·éç³»ç»", value: "auto" },
|
| | | { label: "æµ
è²", value: "light" },
|
| | | { label: "æ·±è²", value: "dark" },
|
| | | ]);
|
| | |
|
| | | /** æ¯å¦éè¦topnav */
|
| | | function topNavChange(val) {
|
| | | if (!val) {
|
| | | appStore.toggleSideBarHide(false);
|
| | | permissionStore.setSidebarRouters(permissionStore.defaultRoutes);
|
| | | /** æ¯å¦éè¦topnav */
|
| | | function topNavChange(val) {
|
| | | if (!val) {
|
| | | appStore.toggleSideBarHide(false);
|
| | | permissionStore.setSidebarRouters(permissionStore.defaultRoutes);
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | function themeChange(val) {
|
| | | settingsStore.theme = val;
|
| | | handleThemeStyle(val);
|
| | | }
|
| | | function themeChange(val) {
|
| | | settingsStore.theme = val;
|
| | | handleThemeStyle(val);
|
| | | }
|
| | |
|
| | | function darkModeChange(val) {
|
| | | settingsStore.setDarkMode(val);
|
| | | }
|
| | | function darkModeChange(val) {
|
| | | settingsStore.setDarkMode(val);
|
| | | }
|
| | |
|
| | | function handleTheme(val) {
|
| | | settingsStore.sideTheme = val;
|
| | | sideTheme.value = val;
|
| | | }
|
| | | function handleTheme(val) {
|
| | | settingsStore.sideTheme = val;
|
| | | sideTheme.value = val;
|
| | | }
|
| | |
|
| | | function saveSetting() {
|
| | | proxy.$modal.loading("æ£å¨ä¿åå°æ¬å°ï¼è¯·ç¨å...");
|
| | | let layoutSetting = {
|
| | | topNav: storeSettings.value.topNav,
|
| | | tagsView: storeSettings.value.tagsView,
|
| | | fixedHeader: storeSettings.value.fixedHeader,
|
| | | sidebarLogo: storeSettings.value.sidebarLogo,
|
| | | dynamicTitle: storeSettings.value.dynamicTitle,
|
| | | sideTheme: storeSettings.value.sideTheme,
|
| | | theme: storeSettings.value.theme,
|
| | | darkMode: storeSettings.value.darkMode,
|
| | | };
|
| | | localStorage.setItem("layout-setting", JSON.stringify(layoutSetting));
|
| | | setTimeout(proxy.$modal.closeLoading(), 1000);
|
| | | }
|
| | | function saveSetting() {
|
| | | proxy.$modal.loading("æ£å¨ä¿åå°æ¬å°ï¼è¯·ç¨å...");
|
| | | let layoutSetting = {
|
| | | topNav: storeSettings.value.topNav,
|
| | | tagsView: storeSettings.value.tagsView,
|
| | | fixedHeader: storeSettings.value.fixedHeader,
|
| | | sidebarLogo: storeSettings.value.sidebarLogo,
|
| | | dynamicTitle: storeSettings.value.dynamicTitle,
|
| | | sideTheme: storeSettings.value.sideTheme,
|
| | | theme: storeSettings.value.theme,
|
| | | darkMode: storeSettings.value.darkMode,
|
| | | };
|
| | | localStorage.setItem("layout-setting", JSON.stringify(layoutSetting));
|
| | | setTimeout(proxy.$modal.closeLoading(), 1000);
|
| | | }
|
| | |
|
| | | function resetSetting() {
|
| | | proxy.$modal.loading("æ£å¨æ¸
é¤è®¾ç½®ç¼åå¹¶å·æ°ï¼è¯·ç¨å...");
|
| | | localStorage.removeItem("layout-setting");
|
| | | setTimeout("window.location.reload()", 1000);
|
| | | }
|
| | | function resetSetting() {
|
| | | proxy.$modal.loading("æ£å¨æ¸
é¤è®¾ç½®ç¼åå¹¶å·æ°ï¼è¯·ç¨å...");
|
| | | localStorage.removeItem("layout-setting");
|
| | | setTimeout("window.location.reload()", 1000);
|
| | | }
|
| | |
|
| | | function openSetting() {
|
| | | showSettings.value = true;
|
| | | }
|
| | | function openSetting() {
|
| | | showSettings.value = true;
|
| | | }
|
| | |
|
| | | defineExpose({
|
| | | openSetting,
|
| | | });
|
| | | defineExpose({
|
| | | openSetting,
|
| | | });
|
| | | </script>
|
| | |
|
| | | <style lang="scss" scoped>
|
| | | .setting-drawer-title {
|
| | | margin-bottom: 12px;
|
| | | color: var(--el-text-color-primary, rgba(0, 0, 0, 0.85));
|
| | | line-height: 22px;
|
| | | font-weight: bold;
|
| | | .setting-drawer-title {
|
| | | margin-bottom: 12px;
|
| | | color: var(--el-text-color-primary, rgba(0, 0, 0, 0.85));
|
| | | line-height: 22px;
|
| | | font-weight: bold;
|
| | |
|
| | | .drawer-title {
|
| | | font-size: 14px;
|
| | | }
|
| | | }
|
| | |
|
| | | .setting-drawer-block-checbox {
|
| | | display: flex;
|
| | | justify-content: flex-start;
|
| | | align-items: center;
|
| | | margin-top: 10px;
|
| | | margin-bottom: 20px;
|
| | |
|
| | | .setting-drawer-block-checbox-item {
|
| | | position: relative;
|
| | | margin-right: 16px;
|
| | | border-radius: 2px;
|
| | | cursor: pointer;
|
| | |
|
| | | img {
|
| | | width: 48px;
|
| | | height: 48px;
|
| | | }
|
| | |
|
| | | .setting-drawer-block-checbox-selectIcon {
|
| | | position: absolute;
|
| | | top: 0;
|
| | | right: 0;
|
| | | width: 100%;
|
| | | height: 100%;
|
| | | padding-top: 15px;
|
| | | padding-left: 24px;
|
| | | color: #1890ff;
|
| | | font-weight: 700;
|
| | | .drawer-title {
|
| | | font-size: 14px;
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | .drawer-item {
|
| | | color: var(--el-text-color-regular, rgba(0, 0, 0, 0.65));
|
| | | padding: 12px 0;
|
| | | font-size: 14px;
|
| | | .setting-drawer-block-checbox {
|
| | | display: flex;
|
| | | justify-content: flex-start;
|
| | | align-items: center;
|
| | | margin-top: 10px;
|
| | | margin-bottom: 20px;
|
| | |
|
| | | .comp-style {
|
| | | float: right;
|
| | | margin: -3px 8px 0px 0px;
|
| | | .setting-drawer-block-checbox-item {
|
| | | position: relative;
|
| | | margin-right: 16px;
|
| | | border-radius: 2px;
|
| | | cursor: pointer;
|
| | |
|
| | | img {
|
| | | width: 48px;
|
| | | height: 48px;
|
| | | }
|
| | |
|
| | | .setting-drawer-block-checbox-selectIcon {
|
| | | position: absolute;
|
| | | top: 0;
|
| | | right: 0;
|
| | | width: 100%;
|
| | | height: 100%;
|
| | | padding-top: 15px;
|
| | | padding-left: 24px;
|
| | | color: #1890ff;
|
| | | font-weight: 700;
|
| | | font-size: 14px;
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | .drawer-item {
|
| | | color: var(--el-text-color-regular, rgba(0, 0, 0, 0.65));
|
| | | padding: 12px 0;
|
| | | font-size: 14px;
|
| | |
|
| | | .comp-style {
|
| | | float: right;
|
| | | margin: -3px 8px 0px 0px;
|
| | | }
|
| | | }
|
| | | </style>
|
| | |
| | | <template> |
| | | <div class="sidebar-logo-container" :class="{ collapse }"> |
| | | <transition name="sidebarLogoFade"> |
| | | <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> |
| | | <router-link style="display: flex;" v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> |
| | | <img :src="faviconUrl" class="sidebar-logo sidebar-favicon" alt="ç«ç¹å¾æ " /> |
| | | </router-link> |
| | | <router-link v-else key="expand" class="sidebar-logo-link" :style="expandLogoLinkStyle" to="/"> |
| | |
| | | .sidebar-logo-container { |
| | | position: relative; |
| | | width: 100% !important; |
| | | height: 78px !important; |
| | | line-height: 78px; |
| | | height: 64px !important; |
| | | line-height: 64px; |
| | | background: transparent; |
| | | border-bottom: 1px solid rgba(255, 255, 255, 0.08); |
| | | text-align: left; |
| | | overflow: hidden; |
| | | box-shadow: none; |
| | | backdrop-filter: none; |
| | | transition: all 0.3s ease; |
| | | |
| | | .sidebar-logo-link { |
| | | position: relative; |
| | | isolation: isolate; |
| | | height: 100%; |
| | | width: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 0; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: 0; |
| | | background-image: var(--logo-bg-image); |
| | | background-size: cover; |
| | | background-position: center; |
| | | opacity: 0.26; |
| | | filter: blur(8px) saturate(0.9); |
| | | transform: scale(1.06); |
| | | pointer-events: none; |
| | | z-index: 0; |
| | | } |
| | | |
| | | &::after { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: 0; |
| | | background: linear-gradient(180deg, rgba(16, 49, 89, 0.18), rgba(16, 49, 89, 0.08)); |
| | | pointer-events: none; |
| | | z-index: 0; |
| | | } |
| | | justify-content: flex-start; // é»è®¤å±å¼æ¶é å·¦ |
| | | padding: 0 16px; |
| | | } |
| | | |
| | | .sidebar-logo { |
| | | width: 100%; |
| | | height: 100%; |
| | | max-width: none; |
| | | max-height: none; |
| | | padding: 6px 10px; |
| | | height:100%; |
| | | width:100%; |
| | | width: auto; |
| | | max-width: 100%; |
| | | vertical-align: middle; |
| | | object-fit: contain; |
| | | object-position: center; |
| | | filter: none; |
| | | display: block; |
| | | position: relative; |
| | | z-index: 1; |
| | | } |
| | | |
| | | .sidebar-title { |
| | | display: inline-block; |
| | | margin: 0; |
| | | color: var(--text-primary); |
| | | color: #fff; |
| | | font-weight: 600; |
| | | line-height: 1.2; |
| | | font-size: 14px; |
| | | font-family: "Segoe UI", "PingFang SC", sans-serif; |
| | | line-height: 64px; |
| | | font-size: 16px; |
| | | vertical-align: middle; |
| | | position: relative; |
| | | z-index: 1; |
| | | margin-left: 12px; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | &.collapse { |
| | | .sidebar-logo-link { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 0; |
| | | |
| | | &::before, |
| | | &::after { |
| | | display: none; |
| | | } |
| | | justify-content: center; // æå æ¶ç»å¯¹å±
ä¸ |
| | | } |
| | | |
| | | .sidebar-logo { |
| | | width: calc(100% - 8px); |
| | | height: calc(100% - 8px); |
| | | max-width: none; |
| | | max-height: none; |
| | | padding: 4px; |
| | | margin: 0 auto; |
| | | filter: none; |
| | | object-fit: contain; |
| | | object-position: center; |
| | | } |
| | | |
| | | .sidebar-favicon { |
| | | width: calc(100% - 8px); |
| | | height: calc(100% - 8px); |
| | | max-width: none; |
| | | max-height: none; |
| | | width: 32px; |
| | | height: 32px; |
| | | margin: 0; |
| | | } |
| | | } |
| | | } |
| | |
| | | }
|
| | | </script>
|
| | |
|
| | | <style lang="scss" scoped>
|
| | | <style lang="scss" scoped> |
| | | .sidebar-item-wrapper { |
| | | :deep(.menu-icon) { |
| | | width: 26px; |
| | | height: 26px; |
| | | width: 18px; |
| | | height: 18px; |
| | | margin-right: 12px; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-shrink: 0; |
| | | transition: color 0.2s ease; |
| | | color: var(--sidebar-text); |
| | | opacity: 0.88; |
| | | transition: all 0.3s ease; |
| | | color: inherit; |
| | | } |
| | | |
| | | :deep(.el-menu-item:hover .menu-icon), |
| | | :deep(.el-sub-menu__title:hover .menu-icon) { |
| | | color: #ffffff; |
| | | opacity: 1; |
| | | } |
| | | |
| | | :deep(.el-menu-item.is-active .menu-icon) {
|
| | | color: var(--menu-active-text) !important;
|
| | | opacity: 1;
|
| | | }
|
| | | |
| | | :deep(.menu-title) { |
| | | font-weight: 500; |
| | | transition: color 0.2s ease; |
| | | color: var(--sidebar-text); |
| | | opacity: 0.82; |
| | | } |
| | | |
| | | :deep(.el-menu-item:hover .menu-title), |
| | | :deep(.el-sub-menu__title:hover .menu-title) { |
| | | color: #ffffff; |
| | | opacity: 1; |
| | | } |
| | | |
| | | :deep(.el-menu-item.is-active .menu-title) { |
| | | color: var(--menu-active-text) !important; |
| | | opacity: 1; |
| | | color: inherit; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | flex: 1; |
| | | } |
| | | |
| | | :deep(.nest-menu) { |
| | | .menu-icon { |
| | | width: 22px; |
| | | height: 22px; |
| | | width: 16px; |
| | | height: 16px; |
| | | margin-right: 10px; |
| | | } |
| | | |
| | | .menu-title { font-size: 13px; } |
| | | .menu-title { |
| | | font-size: 13px; |
| | | } |
| | | } |
| | | } |
| | | </style>
|
| | |
| | | border: none !important; |
| | | height: 100%; |
| | | width: 100% !important; |
| | | border-radius: 0; |
| | | background: transparent !important; |
| | | |
| | | .el-menu-item, |
| | | .el-sub-menu__title { |
| | | margin-bottom: 8px; |
| | | border-radius: 14px; |
| | | color: v-bind(getMenuTextColor); |
| | | :deep(.el-menu-item), |
| | | :deep(.el-sub-menu__title) { |
| | | margin-bottom: 4px; |
| | | border-radius: var(--radius-md); |
| | | color: var(--sidebar-text); |
| | | font-size: 14px; |
| | | letter-spacing: 0; |
| | | transition: |
| | | transform 0.18s ease, |
| | | background 0.2s ease, |
| | | box-shadow 0.2s ease, |
| | | color 0.2s ease; |
| | | border: none !important; |
| | | transition: all 0.2s ease; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | &:hover { |
| | | background: linear-gradient(128deg, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.28), rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.08)) !important; |
| | | transform: translate3d(2px, 0, 0); |
| | | background: var(--menu-hover) !important; |
| | | color: #fff !important; |
| | | } |
| | | } |
| | | |
| | | .el-menu-item { |
| | | color: var(--sidebar-text); |
| | | |
| | | &.is-active { |
| | | background: var(--menu-active-bg, linear-gradient(135deg, var(--el-color-primary), var(--el-color-primary-light-3))) !important; |
| | | color: var(--menu-active-text) !important; |
| | | font-weight: 500; |
| | | border-radius: 14px; |
| | | box-shadow: var(--menu-active-glow, 0 10px 24px rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.34)); |
| | | |
| | | .svg-icon { |
| | | color: var(--menu-active-text) !important; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .el-sub-menu__title { |
| | | color: v-bind(getMenuTextColor); |
| | | } |
| | | |
| | | :deep(.el-sub-menu__icon-arrow) { |
| | | display: inline-flex !important; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 14px; |
| | | height: 14px; |
| | | margin-top: -7px; |
| | | right: 14px; |
| | | font-size: 14px !important; |
| | | color: currentColor !important; |
| | | opacity: 0.7; |
| | | transition: transform 0.2s ease, opacity 0.2s ease; |
| | | } |
| | | |
| | | :deep(.el-sub-menu.is-opened .el-sub-menu__icon-arrow) { |
| | | transform: rotate(180deg); |
| | | } |
| | | |
| | | :deep(.el-sub-menu.is-active > .el-sub-menu__title) { |
| | | color: var(--menu-active-text) !important; |
| | | font-weight: 500; |
| | | border-radius: 12px; |
| | | margin: 0 12px 6px !important; |
| | | padding-left: 14px !important; |
| | | padding-right: 34px !important; |
| | | box-sizing: border-box; |
| | | overflow: hidden; |
| | | background-clip: padding-box; |
| | | background: var(--menu-active-bg) !important; |
| | | box-shadow: var(--menu-active-glow); |
| | | border: none !important; |
| | | } |
| | | |
| | | :deep(.el-menu-item.is-active) { |
| | | margin: 0 12px 6px !important; |
| | | width: calc(100% - 24px) !important; |
| | | padding-left: 14px !important; |
| | | padding-right: 34px !important; |
| | | box-sizing: border-box; |
| | | overflow: hidden; |
| | | background-clip: padding-box; |
| | | border-radius: 12px; |
| | | background: var(--menu-active-bg) !important; |
| | | color: var(--menu-active-text) !important; |
| | | font-weight: 600; |
| | | box-shadow: var(--menu-active-glow); |
| | | |
| | | .svg-icon { |
| | | color: var(--menu-active-text) !important; |
| | | } |
| | | } |
| | | |
| | | :deep(.el-sub-menu.is-active > .el-sub-menu__title .menu-title), |
| | | :deep(.el-sub-menu.is-active > .el-sub-menu__title .svg-icon), |
| | | :deep(.el-menu-item.is-active .menu-title) { |
| | | color: var(--menu-active-text) !important; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | @contextmenu.prevent="openMenu(tag, $event)"
|
| | | >
|
| | | {{ tag.title }}
|
| | | <span v-if="!isAffix(tag)" class="tags-view-close" @click.prevent.stop="closeSelectedTag(tag)"> |
| | | <close class="el-icon-close" /> |
| | | </span> |
| | | <span v-if="!isAffix(tag)" class="tags-view-close" @click.prevent.stop="closeSelectedTag(tag)">
|
| | | <close class="el-icon-close" />
|
| | | </span>
|
| | | </router-link>
|
| | | </scroll-pane>
|
| | | <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
|
| | |
| | | </script>
|
| | |
|
| | | <style lang="scss" scoped>
|
| | | .tags-view-container { |
| | | height: var(--tagsbar-height); |
| | | width: 100%; |
| | | margin-top: 0; |
| | | padding: 0 2px; |
| | | background: transparent; |
| | | border: none; |
| | | border-radius: 0; |
| | | backdrop-filter: none; |
| | | box-shadow: none; |
| | | |
| | | .tags-view-wrapper { |
| | | display: flex; |
| | | align-items: center; |
| | | min-height: var(--tagsbar-height); |
| | | |
| | | .tags-view-item { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | position: relative; |
| | | cursor: pointer; |
| | | height: 30px; |
| | | line-height: 1; |
| | | color: var(--tags-item-text, #4E5463); |
| | | background: var(--tags-item-bg, #E5E7EA); |
| | | border: 1px solid var(--tags-item-border, #d8dce5); |
| | | border-radius: 999px; |
| | | padding: 0 14px; |
| | | font-size: 12px; |
| | | margin-right: 6px; |
| | | flex-shrink: 0; |
| | | gap: 6px; |
| | | transition: all 0.24s ease; |
| | | |
| | | &:hover { |
| | | background: var(--tags-item-hover, #eee); |
| | | border-color: rgba(96, 165, 250, 0.36); |
| | | } |
| | | |
| | | &.active { |
| | | background-image: linear-gradient(132deg, rgba(47, 128, 255, 0.95), rgba(56, 189, 248, 0.9)); |
| | | color: #fff; |
| | | box-shadow: 0 10px 20px rgba(37, 99, 235, 0.22); |
| | | border-color: rgba(147, 197, 253, 0.72) !important; |
| | | } |
| | | } |
| | | } |
| | | .tags-view-container {
|
| | | height: 42px; // å¢å 容å¨é«åº¦ï¼æä¾æ´å¤å¼å¸æ
|
| | | width: 100%;
|
| | | background: var(--tags-bg);
|
| | | border-bottom: 1px solid var(--surface-border);
|
| | | padding: 0 16px;
|
| | | display: flex;
|
| | | align-items: center;
|
| | |
|
| | | .tags-view-wrapper {
|
| | | flex: 1;
|
| | | overflow: hidden;
|
| | | white-space: nowrap;
|
| | |
|
| | | .tags-view-item {
|
| | | display: inline-flex;
|
| | | align-items: center;
|
| | | position: relative;
|
| | | cursor: pointer;
|
| | | height: 30px; // å¢å 页ç¾é«åº¦
|
| | | line-height: 30px;
|
| | | color: var(--tags-item-text);
|
| | | background: var(--tags-item-bg);
|
| | | border: 1px solid var(--tags-item-border);
|
| | | border-radius: 6px; // ç¨å¾®å润ä¸ç¹
|
| | | padding: 0 12px;
|
| | | font-size: 12px;
|
| | | margin-right: 6px;
|
| | | transition: all 0.2s ease;
|
| | | gap: 6px;
|
| | | text-decoration: none;
|
| | |
|
| | | &:hover {
|
| | | background: var(--tags-item-hover);
|
| | | color: var(--accent-primary);
|
| | | border-color: var(--accent-primary);
|
| | | }
|
| | |
|
| | | &.active {
|
| | | background: var(--accent-primary);
|
| | | color: #fff;
|
| | | border-color: var(--accent-primary);
|
| | | box-shadow: 0 2px 8px rgba(37, 99, 235, 0.15);
|
| | | font-weight: 500;
|
| | |
|
| | | &::before {
|
| | | content: "";
|
| | | background: #fff;
|
| | | display: inline-block;
|
| | | width: 6px;
|
| | | height: 6px;
|
| | | border-radius: 50%;
|
| | | margin-right: 2px;
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | .contextmenu {
|
| | | margin: 0;
|
| | | background: var(--el-bg-color-overlay, #fff);
|
| | | background: #fff;
|
| | | z-index: 3000;
|
| | | position: absolute;
|
| | | list-style-type: none;
|
| | | padding: 5px 0;
|
| | | border-radius: 16px; |
| | | padding: 4px 0;
|
| | | border-radius: var(--radius-md);
|
| | | font-size: 12px;
|
| | | font-weight: 400;
|
| | | color: var(--tags-item-text, #333);
|
| | | box-shadow: var(--shadow-md); |
| | | border: 1px solid var(--surface-border, #e4e7ed); |
| | | color: var(--text-secondary);
|
| | | box-shadow: var(--shadow-md);
|
| | | border: 1px solid var(--surface-border);
|
| | |
|
| | | li {
|
| | | margin: 0;
|
| | | padding: 7px 16px;
|
| | | padding: 8px 16px;
|
| | | cursor: pointer;
|
| | | display: flex;
|
| | | align-items: center;
|
| | | gap: 8px;
|
| | | transition: all 0.2s ease;
|
| | | white-space: nowrap;
|
| | |
|
| | | svg {
|
| | | width: 14px;
|
| | | height: 14px;
|
| | | color: inherit;
|
| | | }
|
| | |
|
| | | &:hover {
|
| | | background: var(--tags-item-hover, #eee);
|
| | | background: #f1f5f9;
|
| | | color: var(--accent-primary);
|
| | | }
|
| | | }
|
| | | }
|
| | |
| | |
|
| | | <style lang="scss">
|
| | | //reset element css of el-icon-close
|
| | | .tags-view-wrapper { |
| | | .el-scrollbar__view { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .tags-view-item { |
| | | .tags-view-close { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 12px; |
| | | height: 12px; |
| | | line-height: 1; |
| | | align-self: center; |
| | | transform: translateY(1px); |
| | | } |
| | | |
| | | .el-icon-close { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 12px; |
| | | height: 12px; |
| | | line-height: 1; |
| | | vertical-align: initial !important; |
| | | border-radius: 50%; |
| | | text-align: center; |
| | | transition: all .3s cubic-bezier(.645, .045, .355, 1); |
| | | transform-origin: 100% 50%; |
| | | align-self: center; |
| | | |
| | | &:before { |
| | | transform: scale(.6); |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | svg { |
| | | display: block; |
| | | width: 10px; |
| | | height: 10px; |
| | | } |
| | | |
| | | &:hover { |
| | | background-color: var(--tags-close-hover, #b4bccc); |
| | | color: #fff; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | | .tags-view-wrapper {
|
| | | .el-scrollbar__view {
|
| | | display: flex;
|
| | | align-items: center;
|
| | | }
|
| | |
|
| | | .tags-view-item {
|
| | | .tags-view-close {
|
| | | display: inline-flex;
|
| | | align-items: center;
|
| | | justify-content: center;
|
| | | width: 12px;
|
| | | height: 12px;
|
| | | line-height: 1;
|
| | | align-self: center;
|
| | | transform: translateY(1px);
|
| | | }
|
| | |
|
| | | .el-icon-close {
|
| | | display: inline-flex;
|
| | | align-items: center;
|
| | | justify-content: center;
|
| | | width: 12px;
|
| | | height: 12px;
|
| | | line-height: 1;
|
| | | vertical-align: initial !important;
|
| | | border-radius: 50%;
|
| | | text-align: center;
|
| | | transition: all .3s cubic-bezier(.645, .045, .355, 1);
|
| | | transform-origin: 100% 50%;
|
| | | align-self: center;
|
| | |
|
| | | &:before {
|
| | | transform: scale(.6);
|
| | | display: inline-flex;
|
| | | align-items: center;
|
| | | justify-content: center;
|
| | | }
|
| | |
|
| | | svg {
|
| | | display: block;
|
| | | width: 10px;
|
| | | height: 10px;
|
| | | }
|
| | |
|
| | | &:hover {
|
| | | background-color: var(--tags-close-hover, #b4bccc);
|
| | | color: #fff;
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | | </style>
|
| | |
| | | position: relative; |
| | | min-height: 100%; |
| | | width: 100%; |
| | | background: radial-gradient( |
| | | circle at 14% -8%, |
| | | rgba(59, 130, 246, 0.14), |
| | | transparent 36% |
| | | ), |
| | | radial-gradient( |
| | | circle at 88% -12%, |
| | | rgba(56, 189, 248, 0.1), |
| | | transparent 30% |
| | | ), |
| | | linear-gradient(165deg, #f3f7fc 0%, #eef5ff 56%, #f8fbff 100%); |
| | | background: var(--app-bg); |
| | | |
| | | &.mobile.openSidebar { |
| | | position: fixed; |
| | |
| | | } |
| | | |
| | | .drawer-bg { |
| | | background: rgba(15, 23, 42, 0.22); |
| | | background: rgba(0, 0, 0, 0.3); |
| | | width: 100%; |
| | | top: 0; |
| | | height: 100%; |
| | |
| | | transition: margin-left 0.25s ease; |
| | | display: flex; |
| | | flex-direction: column; |
| | | padding: 0; |
| | | } |
| | | |
| | | .fixed-header { |
| | |
| | | top: 0; |
| | | z-index: var(--layout-header-z); |
| | | width: 100%; |
| | | padding: 8px var(--content-gap) 8px; |
| | | padding: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 6px; |
| | | background: var(--app-bg, #f3f7fc); |
| | | gap: 2px; // å¨ Navbar å TagsView ä¹é´å¢å æå°ç空é |
| | | } |
| | | |
| | | .fixed-header.with-tags { |
| | | padding-bottom: 6px; |
| | | padding-bottom: 4px; // åºé¨çåºä¸ç¹è·ç¦»ï¼ä¸è¦ç´æ¥è´´ç主ä½å
容 |
| | | } |
| | | |
| | | .hideSidebar .fixed-header { |
| | |
| | | emitAuto: true, |
| | | }); |
| | | |
| | | const { |
| | | sideTheme, |
| | | showSettings, |
| | | topNav, |
| | | tagsView, |
| | | fixedHeader, |
| | | sidebarLogo, |
| | | dynamicTitle, |
| | | darkMode, |
| | | } = defaultSettings; |
| | | const { sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle, darkMode } = defaultSettings; |
| | | |
| | | const storageSetting = JSON.parse(localStorage.getItem("layout-setting") || "{}"); |
| | | const defaultDarkMode = darkMode || "auto"; |
| | | const initialDarkMode = storageSetting.darkMode || defaultDarkMode; |
| | | colorMode.value = initialDarkMode; |
| | | const getIsDark = (mode) => mode === "dark" || (mode === "auto" && preferredDark.value); |
| | | const getIsDark = mode => mode === "dark" || (mode === "auto" && preferredDark.value); |
| | | |
| | | const useSettingsStore = defineStore("settings", () => { |
| | | const title = ref(""); |
| | | const theme = ref(storageSetting.theme || "#002fa7"); |
| | | const theme = ref(storageSetting.theme || "#374D77"); |
| | | const sideThemeValue = ref(storageSetting.sideTheme || sideTheme); |
| | | const showSettingsValue = ref(showSettings); |
| | | const topNavValue = ref( |
| | | storageSetting.topNav === undefined ? topNav : storageSetting.topNav |
| | | ); |
| | | const tagsViewValue = ref( |
| | | storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView |
| | | ); |
| | | const fixedHeaderValue = ref( |
| | | storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader |
| | | ); |
| | | const sidebarLogoValue = ref( |
| | | storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo |
| | | ); |
| | | const dynamicTitleValue = ref( |
| | | storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle |
| | | ); |
| | | const topNavValue = ref(storageSetting.topNav === undefined ? topNav : storageSetting.topNav); |
| | | const tagsViewValue = ref(storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView); |
| | | const fixedHeaderValue = ref(storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader); |
| | | const sidebarLogoValue = ref(storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo); |
| | | const dynamicTitleValue = ref(storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle); |
| | | const darkModeValue = ref(initialDarkMode); |
| | | const isDark = computed(() => getIsDark(darkModeValue.value)); |
| | | |
| | |
| | | // å¤ç䏻颿 ·å¼ |
| | | export function handleThemeStyle(theme) { |
| | | const primary = normalizeHex(theme) |
| | | const [r, g, b] = hexToRgb(primary) |
| | | const light2 = getLightColor(primary, 0.2) |
| | | const light3 = getLightColor(primary, 0.3) |
| | | const light5 = getLightColor(primary, 0.5) |
| | | const primary = normalizeHex(theme); |
| | | const [r, g, b] = hexToRgb(primary); |
| | | const light2 = getLightColor(primary, 0.2); |
| | | const light3 = getLightColor(primary, 0.3); |
| | | const light5 = getLightColor(primary, 0.5); |
| | | |
| | | document.documentElement.style.setProperty('--el-color-primary', primary) |
| | | document.documentElement.style.setProperty('--el-color-primary-rgb', `${r}, ${g}, ${b}`) |
| | | for (let i = 1; i <= 9; i++) { |
| | | document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(primary, i / 10)}`) |
| | | } |
| | | for (let i = 1; i <= 9; i++) { |
| | | document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, `${getDarkColor(primary, i / 10)}`) |
| | | } |
| | | document.documentElement.style.setProperty("--el-color-primary", primary); |
| | | document.documentElement.style.setProperty("--el-color-primary-rgb", `${r}, ${g}, ${b}`); |
| | | for (let i = 1; i <= 9; i++) { |
| | | document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(primary, i / 10)}`); |
| | | } |
| | | for (let i = 1; i <= 9; i++) { |
| | | document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, `${getDarkColor(primary, i / 10)}`); |
| | | } |
| | | |
| | | // ç³»ç»ä¸»é¢èå¨å°ä¾§è¾¹æ é䏿ä¸é«äº® |
| | | document.documentElement.style.setProperty('--menu-active-bg', `linear-gradient(135deg, ${primary} 0%, ${light3} 100%)`) |
| | | document.documentElement.style.setProperty('--menu-active-glow', `0 10px 24px rgba(${r}, ${g}, ${b}, 0.32)`) |
| | | document.documentElement.style.setProperty('--menu-hover', `rgba(${r}, ${g}, ${b}, 0.2)`) |
| | | document.documentElement.style.setProperty('--accent-primary', primary) |
| | | document.documentElement.style.setProperty('--accent-light', light2) |
| | | document.documentElement.style.setProperty('--accent-lighter', light5) |
| | | // ç³»ç»ä¸»é¢èå¨å°ä¾§è¾¹æ é䏿ä¸é«äº® |
| | | document.documentElement.style.setProperty("--menu-active-bg", `linear-gradient(135deg, ${primary} 0%, ${light3} 100%)`); |
| | | document.documentElement.style.setProperty("--menu-active-glow", `0 10px 24px rgba(${r}, ${g}, ${b}, 0.32)`); |
| | | document.documentElement.style.setProperty("--menu-hover", `rgba(${r}, ${g}, ${b}, 0.2)`); |
| | | document.documentElement.style.setProperty("--accent-primary", primary); |
| | | document.documentElement.style.setProperty("--accent-light", light2); |
| | | document.documentElement.style.setProperty("--accent-lighter", light5); |
| | | |
| | | // æ°å¢ï¼ä¾§è¾¹æ ä¸å¯¼èªæ 卿è²å½©èå¨ |
| | | // ä¾§è¾¹æ èæ¯ï¼ææ·±ç主é¢è²åä½ï¼å¢å ä¸ç¹æ·±åº¦æ |
| | | const sidebarBg = getDarkColor(primary, 0.15); |
| | | document.documentElement.style.setProperty("--sidebar-bg", sidebarBg); |
| | | |
| | | // å¯¼èªæ èæ¯ï¼å¸¦éæåº¦ç主é¢è²åä½ï¼å®ç°ç»çææ |
| | | document.documentElement.style.setProperty("--navbar-bg", `rgba(${r}, ${g}, ${b}, 0.85)`); |
| | | |
| | | // é¡µç¾æ èæ¯ï¼ææµ
ç主é¢è²åä½ï¼å¢å å¼å¸æ |
| | | const tagsBg = getLightColor(primary, 0.96); |
| | | document.documentElement.style.setProperty("--tags-bg", tagsBg); |
| | | |
| | | // ææ¬é¢è²éé
é»è¾ |
| | | const brightness = (r * 299 + g * 587 + b * 114) / 1000; |
| | | const isDarkTheme = brightness < 128; |
| | | |
| | | // å¯¼èªæ æåé¢è²ï¼æ ¹æ®ä¸»é¢è²äº®åº¦èªå¨åæ¢æ·±æµ
|
| | | document.documentElement.style.setProperty("--navbar-text", isDarkTheme ? "#f8fafc" : "#1e293b"); |
| | | |
| | | // ä¾§è¾¹æ æåä¸å¾æ é¢è² |
| | | document.documentElement.style.setProperty("--sidebar-text", isDarkTheme ? "rgba(255, 255, 255, 0.75)" : "rgba(15, 23, 42, 0.75)"); |
| | | document.documentElement.style.setProperty("--sidebar-muted", isDarkTheme ? "rgba(255, 255, 255, 0.45)" : "rgba(15, 23, 42, 0.45)"); |
| | | |
| | | // æ¿æ´»é¡¹æåé¢è²ï¼å§ç»ä¿æé«å¯¹æ¯åº¦ |
| | | document.documentElement.style.setProperty("--menu-active-text", isDarkTheme ? "#ffffff" : "#ffffff"); // é常主è²è°æ¿æ´»æç¨ç½è² |
| | | |
| | | // æ¬å䏿¿æ´»ç¶æé¢è² |
| | | document.documentElement.style.setProperty("--menu-hover", `rgba(${r}, ${g}, ${b}, ${isDarkTheme ? 0.12 : 0.08})`); |
| | | document.documentElement.style.setProperty("--navbar-hover", `rgba(${r}, ${g}, ${b}, 0.1)`); |
| | | |
| | | // è¾¹æ¡é¢è²èå¨ |
| | | document.documentElement.style.setProperty("--surface-border", `rgba(${r}, ${g}, ${b}, 0.12)`); |
| | | } |
| | | |
| | | // hexé¢è²è½¬rgbé¢è² |
| | | export function hexToRgb(str) { |
| | | str = normalizeHex(str).replace('#', '') |
| | | const hexs = str.match(/../g) || ['40', '9e', 'ff'] |
| | | for (let i = 0; i < 3; i++) { |
| | | hexs[i] = parseInt(hexs[i], 16) |
| | | } |
| | | return hexs |
| | | str = normalizeHex(str).replace("#", ""); |
| | | const hexs = str.match(/../g) || ["40", "9e", "ff"]; |
| | | for (let i = 0; i < 3; i++) { |
| | | hexs[i] = parseInt(hexs[i], 16); |
| | | } |
| | | return hexs; |
| | | } |
| | | |
| | | // rgbé¢è²è½¬hexé¢è² |
| | | export function rgbToHex(r, g, b) { |
| | | const hexs = [r.toString(16), g.toString(16), b.toString(16)] |
| | | for (let i = 0; i < 3; i++) { |
| | | if (hexs[i].length === 1) { |
| | | hexs[i] = `0${hexs[i]}` |
| | | } |
| | | } |
| | | return `#${hexs.join('')}` |
| | | const hexs = [r.toString(16), g.toString(16), b.toString(16)]; |
| | | for (let i = 0; i < 3; i++) { |
| | | if (hexs[i].length === 1) { |
| | | hexs[i] = `0${hexs[i]}`; |
| | | } |
| | | } |
| | | return `#${hexs.join("")}`; |
| | | } |
| | | |
| | | // åæµ
é¢è²å¼ |
| | | export function getLightColor(color, level) { |
| | | const rgb = hexToRgb(color) |
| | | for (let i = 0; i < 3; i++) { |
| | | rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]) |
| | | } |
| | | return rgbToHex(rgb[0], rgb[1], rgb[2]) |
| | | const rgb = hexToRgb(color); |
| | | for (let i = 0; i < 3; i++) { |
| | | rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]); |
| | | } |
| | | return rgbToHex(rgb[0], rgb[1], rgb[2]); |
| | | } |
| | | |
| | | // åæ·±é¢è²å¼ |
| | | export function getDarkColor(color, level) { |
| | | const rgb = hexToRgb(color) |
| | | for (let i = 0; i < 3; i++) { |
| | | rgb[i] = Math.floor(rgb[i] * (1 - level)) |
| | | } |
| | | return rgbToHex(rgb[0], rgb[1], rgb[2]) |
| | | const rgb = hexToRgb(color); |
| | | for (let i = 0; i < 3; i++) { |
| | | rgb[i] = Math.floor(rgb[i] * (1 - level)); |
| | | } |
| | | return rgbToHex(rgb[0], rgb[1], rgb[2]); |
| | | } |
| | | |
| | | function normalizeHex(color) { |
| | | if (!color || typeof color !== 'string') return '#409eff' |
| | | let value = color.trim().replace('#', '') |
| | | if (value.length === 3) { |
| | | value = value.split('').map((s) => s + s).join('') |
| | | } |
| | | if (!/^[0-9a-fA-F]{6}$/.test(value)) return '#409eff' |
| | | return `#${value.toLowerCase()}` |
| | | if (!color || typeof color !== "string") return "#409eff"; |
| | | let value = color.trim().replace("#", ""); |
| | | if (value.length === 3) { |
| | | value = value |
| | | .split("") |
| | | .map(s => s + s) |
| | | .join(""); |
| | | } |
| | | if (!/^[0-9a-fA-F]{6}$/.test(value)) return "#409eff"; |
| | | return `#${value.toLowerCase()}`; |
| | | } |
| | |
| | | <template> |
| | | <el-dialog v-model="dialogVisible" title="å®åå详æ
" width="80%" @close="closeDia"> |
| | | <el-dialog v-model="dialogVisible" |
| | | title="å®åå详æ
" |
| | | width="80%" |
| | | @close="closeDia"> |
| | | <div v-loading="loading"> |
| | | <span class="descriptions">åºç¡èµæ</span> |
| | | <el-descriptions :column="4" border style="margin-top: 10px;"> |
| | | <el-descriptions :column="4" |
| | | border |
| | | style="margin-top: 10px;"> |
| | | <el-descriptions-item label="客æ·åç§°">{{ detail.customerName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å®åç±»å">{{ getDictLabel(classificationOptions, detail.serviceType) }}</el-descriptions-item> |
| | | <el-descriptions-item label="å
³èéå®åå·">{{ detail.salesContractNo || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç´§æ¥ç¨åº¦">{{ getDictLabel(degreeOfUrgencyOptions, detail.urgency) }}</el-descriptions-item> |
| | | <el-descriptions-item label="å·¥åç¼å·">{{ detail.afterSalesServiceNo || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¤çç¶æ"> |
| | | <el-tag :type="detail.status === 1 ? 'danger' : 'success'" size="small"> |
| | | <el-tag :type="detail.status === 1 ? 'danger' : 'success'" |
| | | size="small"> |
| | | {{ detail.status === 1 ? 'å¾
å¤ç' : 'å·²å¤ç' }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç»è®°äºº">{{ detail.checkNickName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å馿¥æ">{{ detail.feedbackDate || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="客æ·è¯æ±" :span="4">{{ detail.proDesc || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="客æ·è¯æ±" |
| | | :span="4">{{ detail.proDesc || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <span class="descriptions">å¤çä¿¡æ¯</span> |
| | | <el-descriptions :column="3" border style="margin-top: 10px;"> |
| | | <el-descriptions :column="3" |
| | | border |
| | | style="margin-top: 10px;"> |
| | | <el-descriptions-item label="å¤ç人">{{ detail.disposeNickName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¤çæ¥æ">{{ detail.disDate || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¤çç»æ" :span="3">{{ detail.disRes || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¤çç»æ" |
| | | :span="3">{{ detail.disRes || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <span class="descriptions">å
³è产å</span> |
| | | <el-table :data="tableData" border style="width: 100%; margin-top: 10px;"> |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column prop="productCategory" label="产å大类" align="center" /> |
| | | <el-table-column prop="specificationModel" label="è§æ ¼åå·" align="center" /> |
| | | <el-table-column prop="unit" label="åä½" align="center" /> |
| | | <el-table-column prop="expressCompany" label="å¿«éå
¬å¸" align="center" /> |
| | | <el-table-column prop="expressNumber" label="å¿«éåå·" align="center" /> |
| | | <el-table-column prop="shippingCarNumber" label="å货车ç" align="center" /> |
| | | <el-table-column prop="shippingDate" label="åè´§æ¥æ" align="center" /> |
| | | <el-table-column prop="quantity" label="å®åæ°é" align="center" /> |
| | | <el-table :data="tableData" |
| | | border |
| | | style="width: 100%; margin-top: 10px;"> |
| | | <el-table-column type="index" |
| | | label="åºå·" |
| | | width="60" |
| | | align="center" /> |
| | | <el-table-column prop="productCategory" |
| | | label="产å大类" |
| | | align="center" /> |
| | | <el-table-column prop="specificationModel" |
| | | label="è§æ ¼åå·" |
| | | align="center" /> |
| | | <el-table-column prop="unit" |
| | | label="åä½" |
| | | align="center" /> |
| | | <el-table-column prop="expressCompany" |
| | | label="å¿«éå
¬å¸" |
| | | align="center" /> |
| | | <el-table-column prop="expressNumber" |
| | | label="å¿«éåå·" |
| | | align="center" /> |
| | | <el-table-column prop="shippingCarNumber" |
| | | label="å货车ç" |
| | | align="center" /> |
| | | <el-table-column prop="shippingDate" |
| | | label="åè´§æ¥æ" |
| | | align="center" /> |
| | | <el-table-column prop="quantity" |
| | | label="å®åæ°é" |
| | | align="center" /> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, getCurrentInstance } from 'vue'; |
| | | import { getAfterSalesServiceById } from '@/api/customerService/index.js'; |
| | | import { ref, computed, getCurrentInstance } from "vue"; |
| | | import { getAfterSalesServiceById } from "@/api/customerService/index.js"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const dialogVisible = ref(false); |
| | | const loading = ref(false); |
| | | const detail = ref({}); |
| | | const tableData = ref([]); |
| | | const { proxy } = getCurrentInstance(); |
| | | const dialogVisible = ref(false); |
| | | const loading = ref(false); |
| | | const detail = ref({}); |
| | | const tableData = ref([]); |
| | | |
| | | const { post_sale_waiting_list, degree_of_urgency } = proxy.useDict( |
| | | "post_sale_waiting_list", |
| | | "degree_of_urgency" |
| | | ); |
| | | const classificationOptions = computed(() => post_sale_waiting_list?.value || []); |
| | | const degreeOfUrgencyOptions = computed(() => degree_of_urgency?.value || []); |
| | | const { post_sale_waiting_list, degree_of_urgency } = proxy.useDict( |
| | | "post_sale_waiting_list", |
| | | "degree_of_urgency" |
| | | ); |
| | | const classificationOptions = computed( |
| | | () => post_sale_waiting_list?.value || [] |
| | | ); |
| | | const degreeOfUrgencyOptions = computed(() => degree_of_urgency?.value || []); |
| | | |
| | | const getDictLabel = (options, value) => { |
| | | if (!value) return '-'; |
| | | const item = options.find(i => String(i.value) === String(value)); |
| | | return item ? item.label : value; |
| | | }; |
| | | const getDictLabel = (options, value) => { |
| | | if (!value) return "-"; |
| | | const item = options.find(i => String(i.value) === String(value)); |
| | | return item ? item.label : value; |
| | | }; |
| | | |
| | | const openDialog = (row) => { |
| | | dialogVisible.value = true; |
| | | loading.value = true; |
| | | detail.value = {}; |
| | | tableData.value = []; |
| | | |
| | | getAfterSalesServiceById(row.id).then(res => { |
| | | loading.value = false; |
| | | if (res.code === 200) { |
| | | detail.value = res.data || {}; |
| | | let productData = res.data?.salesLedgerDto?.productData || []; |
| | | const selectedIds = res.data.productModelIds ? String(res.data.productModelIds).split(",") : []; |
| | | tableData.value = productData.filter(item => selectedIds.includes(String(item.id))); |
| | | } |
| | | }).catch(() => { |
| | | loading.value = false; |
| | | const openDialog = row => { |
| | | dialogVisible.value = true; |
| | | loading.value = true; |
| | | detail.value = {}; |
| | | tableData.value = []; |
| | | |
| | | getAfterSalesServiceById(row.id) |
| | | .then(res => { |
| | | loading.value = false; |
| | | if (res.code === 200) { |
| | | detail.value = res.data || {}; |
| | | let productData = res.data?.salesLedgerDto?.productData || []; |
| | | const selectedIds = res.data.productModelIds |
| | | ? String(res.data.productModelIds).split(",") |
| | | : []; |
| | | tableData.value = productData.filter(item => |
| | | selectedIds.includes(String(item.id)) |
| | | ); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | loading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const closeDia = () => { |
| | | dialogVisible.value = false; |
| | | }; |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | }; |
| | | |
| | | const closeDia = () => { |
| | | dialogVisible.value = false; |
| | | }; |
| | | |
| | | defineExpose({ |
| | | openDialog |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .descriptions { |
| | | display: inline-block; |
| | | font-size: 1rem; |
| | | font-weight: 600; |
| | | padding-left: 12px; |
| | | position: relative; |
| | | } |
| | | .descriptions::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 4px; |
| | | height: 1rem; |
| | | background-color: #002fa7; |
| | | border-radius: 2px; |
| | | } |
| | | .descriptions { |
| | | display: inline-block; |
| | | font-size: 1rem; |
| | | font-weight: 600; |
| | | padding-left: 12px; |
| | | position: relative; |
| | | } |
| | | .descriptions::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 4px; |
| | | height: 1rem; |
| | | background-color: #374d77; |
| | | border-radius: 2px; |
| | | } |
| | | </style> |
| | |
| | | </el-tag> |
| | | </template> |
| | | <template #quantity="{ row }"> |
| | | <el-input-number v-model="row.quantity" :min="0" size="small" /> |
| | | <el-input-number v-model="row.quantity" |
| | | :min="0" |
| | | size="small" /> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | |
| | | ); |
| | | if (opt) { |
| | | let restoredData = (opt.productData || []).map(normalizeProductRow); |
| | | const selectedIds = form.value.productModelIds ? String(form.value.productModelIds).split(",") : []; |
| | | const quantities = form.value.productModelQuantities ? String(form.value.productModelQuantities).split(",") : []; |
| | | tableData.value = restoredData.filter(item => selectedIds.includes(String(item.id))).map(item => { |
| | | let qIndex = selectedIds.indexOf(String(item.id)); |
| | | if (qIndex !== -1 && qIndex < quantities.length && quantities[qIndex] !== "") { |
| | | item.quantity = Number(quantities[qIndex]); |
| | | } |
| | | return item; |
| | | }); |
| | | const selectedIds = form.value.productModelIds |
| | | ? String(form.value.productModelIds).split(",") |
| | | : []; |
| | | const quantities = form.value.productModelQuantities |
| | | ? String(form.value.productModelQuantities).split(",") |
| | | : []; |
| | | tableData.value = restoredData |
| | | .filter(item => selectedIds.includes(String(item.id))) |
| | | .map(item => { |
| | | let qIndex = selectedIds.indexOf(String(item.id)); |
| | | if ( |
| | | qIndex !== -1 && |
| | | qIndex < quantities.length && |
| | | quantities[qIndex] !== "" |
| | | ) { |
| | | item.quantity = Number(quantities[qIndex]); |
| | | } |
| | | return item; |
| | | }); |
| | | } |
| | | } |
| | | } |
| | |
| | | transform: translateY(-50%); |
| | | width: 4px; |
| | | height: 1rem; |
| | | background-color: #002fa7; /* Element é»è®¤çº¢è² */ |
| | | background-color: #374d77; /* Element é»è®¤çº¢è² */ |
| | | border-radius: 2px; |
| | | } |
| | | </style> |
| | |
| | | <section class="factory"> |
| | | <div class="brand hero-brand"> |
| | | <div class="logo hero-logo"> |
| | | <img |
| | | :src="brandLogoUrl" |
| | | :alt="`${companyName} logo`" |
| | | class="logo-image hero-logo-image" |
| | | @error="handleLogoError" |
| | | /> |
| | | <img :src="brandLogoUrl" |
| | | :alt="`${companyName} logo`" |
| | | class="logo-image hero-logo-image" |
| | | @error="handleLogoError" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="hero"> |
| | | <div class="chip">æ°åå·¥å · æºè½æäº§ · 设å¤äºè · è´¨é追溯</div> |
| | | <h1>æ°åå·¥å<br />MOM æºé å¹³å°</h1> |
| | |
| | | 以宿¶æ°æ®é©±å¨ç产ç°åºï¼æå·¥åã设å¤ãç©æãè´¨éãè½èä¸ä»å¨è¿æ¥æä¸å¼ éæçå¶é è¿è¥ç½ç»ã |
| | | </p> |
| | | </div> |
| | | |
| | | <div class="scene" aria-hidden="true"> |
| | | <div class="scene" |
| | | aria-hidden="true"> |
| | | <div class="floor"></div> |
| | | <svg class="factory-svg" viewBox="0 0 920 360" preserveAspectRatio="xMidYMid meet"> |
| | | <svg class="factory-svg" |
| | | viewBox="0 0 920 360" |
| | | preserveAspectRatio="xMidYMid meet"> |
| | | <defs> |
| | | <linearGradient id="g1" x1="0" y1="0" x2="1" y2="1"> |
| | | <stop offset="0" stop-color="#40e4ff" stop-opacity=".9" /> |
| | | <stop offset="1" stop-color="#1f78ff" stop-opacity=".68" /> |
| | | <linearGradient id="g1" |
| | | x1="0" |
| | | y1="0" |
| | | x2="1" |
| | | y2="1"> |
| | | <stop offset="0" |
| | | stop-color="#40e4ff" |
| | | stop-opacity=".9" /> |
| | | <stop offset="1" |
| | | stop-color="#1f78ff" |
| | | stop-opacity=".68" /> |
| | | </linearGradient> |
| | | <linearGradient id="g2" x1="0" y1="0" x2="1" y2="1"> |
| | | <stop offset="0" stop-color="#ffffff" stop-opacity=".28" /> |
| | | <stop offset="1" stop-color="#ffffff" stop-opacity=".06" /> |
| | | <linearGradient id="g2" |
| | | x1="0" |
| | | y1="0" |
| | | x2="1" |
| | | y2="1"> |
| | | <stop offset="0" |
| | | stop-color="#ffffff" |
| | | stop-opacity=".28" /> |
| | | <stop offset="1" |
| | | stop-color="#ffffff" |
| | | stop-opacity=".06" /> |
| | | </linearGradient> |
| | | </defs> |
| | | |
| | | <path d="M45 255H830" stroke="url(#g1)" stroke-width="16" stroke-linecap="round" opacity=".55" /> |
| | | <path class="belt" d="M45 255H830" stroke="#fff" stroke-width="3" stroke-linecap="round" opacity=".75" /> |
| | | |
| | | <path d="M45 255H830" |
| | | stroke="url(#g1)" |
| | | stroke-width="16" |
| | | stroke-linecap="round" |
| | | opacity=".55" /> |
| | | <path class="belt" |
| | | d="M45 255H830" |
| | | stroke="#fff" |
| | | stroke-width="3" |
| | | stroke-linecap="round" |
| | | opacity=".75" /> |
| | | <g class="box"> |
| | | <rect x="60" y="212" width="54" height="42" rx="8" fill="#ffb15f" /> |
| | | <path d="M60 225h54" stroke="#fff" opacity=".45" /> |
| | | <rect x="60" |
| | | y="212" |
| | | width="54" |
| | | height="42" |
| | | rx="8" |
| | | fill="#ffb15f" /> |
| | | <path d="M60 225h54" |
| | | stroke="#fff" |
| | | opacity=".45" /> |
| | | </g> |
| | | <g class="box two"> |
| | | <rect x="60" y="212" width="54" height="42" rx="8" fill="#28d9cd" /> |
| | | <path d="M60 225h54" stroke="#fff" opacity=".45" /> |
| | | <rect x="60" |
| | | y="212" |
| | | width="54" |
| | | height="42" |
| | | rx="8" |
| | | fill="#28d9cd" /> |
| | | <path d="M60 225h54" |
| | | stroke="#fff" |
| | | opacity=".45" /> |
| | | </g> |
| | | <g class="box three"> |
| | | <rect x="60" y="212" width="54" height="42" rx="8" fill="#8b5cf6" /> |
| | | <path d="M60 225h54" stroke="#fff" opacity=".45" /> |
| | | <rect x="60" |
| | | y="212" |
| | | width="54" |
| | | height="42" |
| | | rx="8" |
| | | fill="#8b5cf6" /> |
| | | <path d="M60 225h54" |
| | | stroke="#fff" |
| | | opacity=".45" /> |
| | | </g> |
| | | |
| | | <g> |
| | | <rect x="120" y="112" width="138" height="128" rx="18" fill="url(#g2)" stroke="rgba(255,255,255,.42)" /> |
| | | <path d="M145 185h88M145 210h58" stroke="#40e4ff" stroke-width="6" stroke-linecap="round" /> |
| | | <path d="M145 140h88" stroke="#fff" stroke-opacity=".5" stroke-width="4" stroke-linecap="round" /> |
| | | <rect x="120" |
| | | y="112" |
| | | width="138" |
| | | height="128" |
| | | rx="18" |
| | | fill="url(#g2)" |
| | | stroke="rgba(255,255,255,.42)" /> |
| | | <path d="M145 185h88M145 210h58" |
| | | stroke="#40e4ff" |
| | | stroke-width="6" |
| | | stroke-linecap="round" /> |
| | | <path d="M145 140h88" |
| | | stroke="#fff" |
| | | stroke-opacity=".5" |
| | | stroke-width="4" |
| | | stroke-linecap="round" /> |
| | | </g> |
| | | |
| | | <g> |
| | | <rect x="315" y="76" width="190" height="164" rx="22" fill="url(#g2)" stroke="rgba(255,255,255,.42)" /> |
| | | <path d="M350 126h120M350 158h90M350 190h112" stroke="#fff" stroke-opacity=".5" stroke-width="6" stroke-linecap="round" /> |
| | | <circle class="signal" cx="472" cy="104" r="10" fill="#20e0d2" /> |
| | | <circle class="signal two" cx="448" cy="104" r="10" fill="#1f78ff" /> |
| | | <circle class="signal three" cx="424" cy="104" r="10" fill="#ff8a3d" /> |
| | | <rect x="315" |
| | | y="76" |
| | | width="190" |
| | | height="164" |
| | | rx="22" |
| | | fill="url(#g2)" |
| | | stroke="rgba(255,255,255,.42)" /> |
| | | <path d="M350 126h120M350 158h90M350 190h112" |
| | | stroke="#fff" |
| | | stroke-opacity=".5" |
| | | stroke-width="6" |
| | | stroke-linecap="round" /> |
| | | <circle class="signal" |
| | | cx="472" |
| | | cy="104" |
| | | r="10" |
| | | fill="#20e0d2" /> |
| | | <circle class="signal two" |
| | | cx="448" |
| | | cy="104" |
| | | r="10" |
| | | fill="#1f78ff" /> |
| | | <circle class="signal three" |
| | | cx="424" |
| | | cy="104" |
| | | r="10" |
| | | fill="#ff8a3d" /> |
| | | </g> |
| | | |
| | | <g class="arm"> |
| | | <path d="M612 124h92" stroke="#40e4ff" stroke-width="14" stroke-linecap="round" /> |
| | | <path d="M704 124l42 56" stroke="#40e4ff" stroke-width="14" stroke-linecap="round" /> |
| | | <circle cx="612" cy="124" r="25" fill="#1f78ff" stroke="#fff" stroke-opacity=".45" /> |
| | | <circle cx="704" cy="124" r="18" fill="#20e0d2" /> |
| | | <path d="M744 180v34M727 214h34" stroke="#fff" stroke-width="7" stroke-linecap="round" /> |
| | | <path d="M612 124h92" |
| | | stroke="#40e4ff" |
| | | stroke-width="14" |
| | | stroke-linecap="round" /> |
| | | <path d="M704 124l42 56" |
| | | stroke="#40e4ff" |
| | | stroke-width="14" |
| | | stroke-linecap="round" /> |
| | | <circle cx="612" |
| | | cy="124" |
| | | r="25" |
| | | fill="#1f78ff" |
| | | stroke="#fff" |
| | | stroke-opacity=".45" /> |
| | | <circle cx="704" |
| | | cy="124" |
| | | r="18" |
| | | fill="#20e0d2" /> |
| | | <path d="M744 180v34M727 214h34" |
| | | stroke="#fff" |
| | | stroke-width="7" |
| | | stroke-linecap="round" /> |
| | | </g> |
| | | |
| | | <g> |
| | | <rect x="690" y="82" width="148" height="158" rx="20" fill="url(#g2)" stroke="rgba(255,255,255,.42)" /> |
| | | <path d="M724 206V134M764 206V112M804 206V154" stroke="#20e0d2" stroke-width="12" stroke-linecap="round" /> |
| | | <path d="M720 206h92" stroke="#fff" stroke-opacity=".44" stroke-width="5" stroke-linecap="round" /> |
| | | <rect x="690" |
| | | y="82" |
| | | width="148" |
| | | height="158" |
| | | rx="20" |
| | | fill="url(#g2)" |
| | | stroke="rgba(255,255,255,.42)" /> |
| | | <path d="M724 206V134M764 206V112M804 206V154" |
| | | stroke="#20e0d2" |
| | | stroke-width="12" |
| | | stroke-linecap="round" /> |
| | | <path d="M720 206h92" |
| | | stroke="#fff" |
| | | stroke-opacity=".44" |
| | | stroke-width="5" |
| | | stroke-linecap="round" /> |
| | | </g> |
| | | |
| | | <path |
| | | d="M190 112C265 42 348 48 410 76C502 118 568 76 612 124C654 170 700 74 764 82" |
| | | fill="none" |
| | | stroke="#20e0d2" |
| | | stroke-width="2" |
| | | stroke-dasharray="8 10" |
| | | opacity=".58" |
| | | > |
| | | <animate attributeName="stroke-dashoffset" from="80" to="0" dur="2s" repeatCount="indefinite" /> |
| | | <path d="M190 112C265 42 348 48 410 76C502 118 568 76 612 124C654 170 700 74 764 82" |
| | | fill="none" |
| | | stroke="#20e0d2" |
| | | stroke-width="2" |
| | | stroke-dasharray="8 10" |
| | | opacity=".58"> |
| | | <animate attributeName="stroke-dashoffset" |
| | | from="80" |
| | | to="0" |
| | | dur="2s" |
| | | repeatCount="indefinite" /> |
| | | </path> |
| | | </svg> |
| | | </div> |
| | | </section> |
| | | |
| | | <section class="login-wrap"> |
| | | <div class="time"> |
| | | <span>{{ todayLabel }}</span> |
| | | {{ clockLabel }} |
| | | </div> |
| | | |
| | | <form class="login-card" @submit.prevent="handleLogin"> |
| | | <form class="login-card" |
| | | @submit.prevent="handleLogin"> |
| | | <div class="brand card-brand"> |
| | | <div class="logo"> |
| | | <img :src="brandIconUrl" :alt="`${companyName} icon`" class="logo-image card-logo-image" /> |
| | | <img :src="brandIconUrl" |
| | | :alt="`${companyName} icon`" |
| | | class="logo-image card-logo-image" /> |
| | | </div> |
| | | <div class="brand-copy card-brand-copy"> |
| | | <div class="brand-title">{{ companyName }}</div> |
| | | <small>æ°åå·¥åç»ä¸å
¥å£</small> |
| | | </div> |
| | | <div class="brand-copy card-brand-copy"> |
| | | <div class="brand-title">{{ companyName }}</div> |
| | | <small>æ°åå·¥åç»ä¸å
¥å£</small> |
| | | </div> |
| | | </div> |
| | | |
| | | <h2>欢è¿ç»å½</h2> |
| | | <p class="sub">è¿å
¥ MOM æ°åå·¥åè¿è¥é©¾é©¶è±</p> |
| | | |
| | | <div class="form-row"> |
| | | <label>è´¦å·</label> |
| | | <div class="input"> |
| | | <svg viewBox="0 0 24 24" fill="none"> |
| | | <path d="M20 21a8 8 0 0 0-16 0" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" /> |
| | | <path d="M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" stroke="currentColor" stroke-width="1.8" /> |
| | | <svg viewBox="0 0 24 24" |
| | | fill="none"> |
| | | <path d="M20 21a8 8 0 0 0-16 0" |
| | | stroke="currentColor" |
| | | stroke-width="1.8" |
| | | stroke-linecap="round" /> |
| | | <path d="M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" |
| | | stroke="currentColor" |
| | | stroke-width="1.8" /> |
| | | </svg> |
| | | <input v-model.trim="loginForm.username" type="text" placeholder="请è¾å
¥ç®¡çåè´¦å·" /> |
| | | <input v-model.trim="loginForm.username" |
| | | type="text" |
| | | placeholder="请è¾å
¥ç®¡çåè´¦å·" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="form-row"> |
| | | <label>å¯ç </label> |
| | | <div class="input"> |
| | | <svg viewBox="0 0 24 24" fill="none"> |
| | | <path d="M7 10V8a5 5 0 0 1 10 0v2" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" /> |
| | | <path d="M6.8 10h10.4A1.8 1.8 0 0 1 19 11.8v6.4A1.8 1.8 0 0 1 17.2 20H6.8A1.8 1.8 0 0 1 5 18.2v-6.4A1.8 1.8 0 0 1 6.8 10Z" stroke="currentColor" stroke-width="1.8" /> |
| | | <svg viewBox="0 0 24 24" |
| | | fill="none"> |
| | | <path d="M7 10V8a5 5 0 0 1 10 0v2" |
| | | stroke="currentColor" |
| | | stroke-width="1.8" |
| | | stroke-linecap="round" /> |
| | | <path d="M6.8 10h10.4A1.8 1.8 0 0 1 19 11.8v6.4A1.8 1.8 0 0 1 17.2 20H6.8A1.8 1.8 0 0 1 5 18.2v-6.4A1.8 1.8 0 0 1 6.8 10Z" |
| | | stroke="currentColor" |
| | | stroke-width="1.8" /> |
| | | </svg> |
| | | <input |
| | | v-model="loginForm.password" |
| | | type="password" |
| | | placeholder="请è¾å
¥ç»å½å¯ç " |
| | | autocomplete="current-password" |
| | | @keyup.enter="handleLogin" |
| | | /> |
| | | <input v-model="loginForm.password" |
| | | type="password" |
| | | placeholder="请è¾å
¥ç»å½å¯ç " |
| | | autocomplete="current-password" |
| | | @keyup.enter="handleLogin" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="options"> |
| | | <label class="check"><input v-model="loginForm.rememberMe" type="checkbox" />è®°ä½è´¦å·</label> |
| | | <label class="check"><input v-model="loginForm.rememberMe" |
| | | type="checkbox" />è®°ä½è´¦å·</label> |
| | | </div> |
| | | |
| | | <button class="login-btn" type="submit" :disabled="loading"> |
| | | <button class="login-btn" |
| | | type="submit" |
| | | :disabled="loading"> |
| | | {{ loading ? "ç»å½ä¸..." : "ç»å½æ°åå·¥å" }} |
| | | </button> |
| | | </form> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ElMessage } from "element-plus" |
| | | import Cookies from "js-cookie" |
| | | import { encrypt, decrypt } from "@/utils/jsencrypt" |
| | | import useUserStore from "@/store/modules/user" |
| | | import defaultBrandLogo from "@/assets/logo/logo.png" |
| | | import { ElMessage } from "element-plus"; |
| | | import Cookies from "js-cookie"; |
| | | import { encrypt, decrypt } from "@/utils/jsencrypt"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import defaultBrandLogo from "@/assets/logo/logo.png"; |
| | | |
| | | const userStore = useUserStore() |
| | | const route = useRoute() |
| | | const router = useRouter() |
| | | const userStore = useUserStore(); |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | | |
| | | const appTitle = String(import.meta.env.VITE_APP_TITLE || "æ°åå·¥å MOM ç³»ç»").trim() |
| | | const companySubtitle = String(import.meta.env.VITE_LOGIN_SUBTITLE || "Digital Factory Operation Center").trim() |
| | | const configuredLogo = String(import.meta.env.VITE_APP_LOGO || "").trim() |
| | | const logoModules = import.meta.glob("/src/assets/logo/*.png", { eager: true }) |
| | | const brandIconUrl = `${import.meta.env.BASE_URL}favicon.ico` |
| | | const appTitle = String( |
| | | import.meta.env.VITE_APP_TITLE || "æ°åå·¥å MOM ç³»ç»" |
| | | ).trim(); |
| | | const companySubtitle = String( |
| | | import.meta.env.VITE_LOGIN_SUBTITLE || "Digital Factory Operation Center" |
| | | ).trim(); |
| | | const configuredLogo = String(import.meta.env.VITE_APP_LOGO || "").trim(); |
| | | const logoModules = import.meta.glob("/src/assets/logo/*.png", { eager: true }); |
| | | const brandIconUrl = `${import.meta.env.BASE_URL}favicon.ico`; |
| | | |
| | | const redirect = ref("") |
| | | const loading = ref(false) |
| | | const now = ref(new Date()) |
| | | const brandLogoUrl = ref(defaultBrandLogo) |
| | | const redirect = ref(""); |
| | | const loading = ref(false); |
| | | const now = ref(new Date()); |
| | | const brandLogoUrl = ref(defaultBrandLogo); |
| | | |
| | | const loginForm = ref({ |
| | | username: "", |
| | | password: "", |
| | | rememberMe: false, |
| | | }) |
| | | const loginForm = ref({ |
| | | username: "", |
| | | password: "", |
| | | rememberMe: false, |
| | | }); |
| | | |
| | | const companyName = computed(() => { |
| | | const currentFactoryName = String(userStore.currentFactoryName || "").trim() |
| | | return currentFactoryName || appTitle |
| | | }) |
| | | const companyName = computed(() => { |
| | | const currentFactoryName = String(userStore.currentFactoryName || "").trim(); |
| | | return currentFactoryName || appTitle; |
| | | }); |
| | | |
| | | const todayLabel = computed(() => { |
| | | const date = now.value |
| | | return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` |
| | | }) |
| | | const todayLabel = computed(() => { |
| | | const date = now.value; |
| | | return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad( |
| | | date.getDate() |
| | | )}`; |
| | | }); |
| | | |
| | | const clockLabel = computed(() => { |
| | | const date = now.value |
| | | return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` |
| | | }) |
| | | const clockLabel = computed(() => { |
| | | const date = now.value; |
| | | return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad( |
| | | date.getSeconds() |
| | | )}`; |
| | | }); |
| | | |
| | | watch( |
| | | route, |
| | | (newRoute) => { |
| | | redirect.value = String(newRoute.query?.redirect || "") |
| | | }, |
| | | { immediate: true } |
| | | ) |
| | | watch( |
| | | route, |
| | | newRoute => { |
| | | redirect.value = String(newRoute.query?.redirect || ""); |
| | | }, |
| | | { immediate: true } |
| | | ); |
| | | |
| | | watch( |
| | | () => userStore.currentFactoryName, |
| | | () => updateBrandLogo(), |
| | | { immediate: true } |
| | | ) |
| | | watch( |
| | | () => userStore.currentFactoryName, |
| | | () => updateBrandLogo(), |
| | | { immediate: true } |
| | | ); |
| | | |
| | | let timer = 0 |
| | | onMounted(() => { |
| | | timer = window.setInterval(() => { |
| | | now.value = new Date() |
| | | }, 1000) |
| | | }) |
| | | let timer = 0; |
| | | onMounted(() => { |
| | | timer = window.setInterval(() => { |
| | | now.value = new Date(); |
| | | }, 1000); |
| | | }); |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (timer) { |
| | | window.clearInterval(timer) |
| | | timer = 0 |
| | | } |
| | | }) |
| | | onBeforeUnmount(() => { |
| | | if (timer) { |
| | | window.clearInterval(timer); |
| | | timer = 0; |
| | | } |
| | | }); |
| | | |
| | | function pad(value) { |
| | | return String(value).padStart(2, "0") |
| | | } |
| | | |
| | | function resolveConfiguredLogo() { |
| | | if (!configuredLogo) { |
| | | return "" |
| | | function pad(value) { |
| | | return String(value).padStart(2, "0"); |
| | | } |
| | | |
| | | if (/^(https?:)?\/\//.test(configuredLogo) || configuredLogo.startsWith("data:")) { |
| | | return configuredLogo |
| | | function resolveConfiguredLogo() { |
| | | if (!configuredLogo) { |
| | | return ""; |
| | | } |
| | | |
| | | if ( |
| | | /^(https?:)?\/\//.test(configuredLogo) || |
| | | configuredLogo.startsWith("data:") |
| | | ) { |
| | | return configuredLogo; |
| | | } |
| | | |
| | | const cleanPath = configuredLogo.replace(/^\/+/, ""); |
| | | const fullPath = cleanPath.startsWith("src/") |
| | | ? `/${cleanPath}` |
| | | : `/src/${cleanPath}`; |
| | | const localLogo = logoModules[fullPath]; |
| | | |
| | | if (localLogo && localLogo.default) { |
| | | return localLogo.default; |
| | | } |
| | | |
| | | if (configuredLogo.startsWith("/")) { |
| | | return configuredLogo; |
| | | } |
| | | |
| | | return `${import.meta.env.BASE_URL}${cleanPath}`; |
| | | } |
| | | |
| | | const cleanPath = configuredLogo.replace(/^\/+/, "") |
| | | const fullPath = cleanPath.startsWith("src/") ? `/${cleanPath}` : `/src/${cleanPath}` |
| | | const localLogo = logoModules[fullPath] |
| | | function updateBrandLogo() { |
| | | const logoFromConfig = resolveConfiguredLogo(); |
| | | if (logoFromConfig) { |
| | | brandLogoUrl.value = logoFromConfig; |
| | | return; |
| | | } |
| | | |
| | | if (localLogo && localLogo.default) { |
| | | return localLogo.default |
| | | const currentFactoryName = String(userStore.currentFactoryName || "").trim(); |
| | | if (!currentFactoryName) { |
| | | brandLogoUrl.value = defaultBrandLogo; |
| | | return; |
| | | } |
| | | |
| | | const factoryLogoPath = `/src/assets/logo/${currentFactoryName}.png`; |
| | | const matched = logoModules[factoryLogoPath]; |
| | | brandLogoUrl.value = |
| | | matched && matched.default ? matched.default : defaultBrandLogo; |
| | | } |
| | | |
| | | if (configuredLogo.startsWith("/")) { |
| | | return configuredLogo |
| | | function handleLogoError() { |
| | | brandLogoUrl.value = defaultBrandLogo; |
| | | } |
| | | |
| | | return `${import.meta.env.BASE_URL}${cleanPath}` |
| | | } |
| | | function handleRememberCookie() { |
| | | if (!loginForm.value.rememberMe) { |
| | | Cookies.remove("username"); |
| | | Cookies.remove("password"); |
| | | Cookies.remove("rememberMe"); |
| | | return; |
| | | } |
| | | |
| | | function updateBrandLogo() { |
| | | const logoFromConfig = resolveConfiguredLogo() |
| | | if (logoFromConfig) { |
| | | brandLogoUrl.value = logoFromConfig |
| | | return |
| | | Cookies.set("username", loginForm.value.username, { expires: 30 }); |
| | | Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 }); |
| | | Cookies.set("rememberMe", "true", { expires: 30 }); |
| | | } |
| | | |
| | | const currentFactoryName = String(userStore.currentFactoryName || "").trim() |
| | | if (!currentFactoryName) { |
| | | brandLogoUrl.value = defaultBrandLogo |
| | | return |
| | | function getCookie() { |
| | | const username = Cookies.get("username"); |
| | | const password = Cookies.get("password"); |
| | | const rememberMe = Cookies.get("rememberMe"); |
| | | |
| | | loginForm.value.username = username || ""; |
| | | loginForm.value.password = password ? decrypt(password) : ""; |
| | | loginForm.value.rememberMe = rememberMe === "true"; |
| | | } |
| | | |
| | | const factoryLogoPath = `/src/assets/logo/${currentFactoryName}.png` |
| | | const matched = logoModules[factoryLogoPath] |
| | | brandLogoUrl.value = matched && matched.default ? matched.default : defaultBrandLogo |
| | | } |
| | | function handleLogin() { |
| | | if (!loginForm.value.username) { |
| | | ElMessage.error("请è¾å
¥è´¦å·"); |
| | | return; |
| | | } |
| | | if (!loginForm.value.password) { |
| | | ElMessage.error("请è¾å
¥å¯ç "); |
| | | return; |
| | | } |
| | | |
| | | function handleLogoError() { |
| | | brandLogoUrl.value = defaultBrandLogo |
| | | } |
| | | |
| | | function handleRememberCookie() { |
| | | if (!loginForm.value.rememberMe) { |
| | | Cookies.remove("username") |
| | | Cookies.remove("password") |
| | | Cookies.remove("rememberMe") |
| | | return |
| | | } |
| | | |
| | | Cookies.set("username", loginForm.value.username, { expires: 30 }) |
| | | Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 }) |
| | | Cookies.set("rememberMe", "true", { expires: 30 }) |
| | | } |
| | | |
| | | function getCookie() { |
| | | const username = Cookies.get("username") |
| | | const password = Cookies.get("password") |
| | | const rememberMe = Cookies.get("rememberMe") |
| | | |
| | | loginForm.value.username = username || "" |
| | | loginForm.value.password = password ? decrypt(password) : "" |
| | | loginForm.value.rememberMe = rememberMe === "true" |
| | | } |
| | | |
| | | function handleLogin() { |
| | | if (!loginForm.value.username) { |
| | | ElMessage.error("请è¾å
¥è´¦å·") |
| | | return |
| | | } |
| | | if (!loginForm.value.password) { |
| | | ElMessage.error("请è¾å
¥å¯ç ") |
| | | return |
| | | } |
| | | |
| | | loading.value = true |
| | | handleRememberCookie() |
| | | loading.value = true; |
| | | handleRememberCookie(); |
| | | userStore |
| | | .loginCheckFactory(loginForm.value) |
| | | .then(() => { |
| | | const query = route.query |
| | | const otherQueryParams = Object.keys(query).reduce((acc, cur) => { |
| | | if (cur !== "redirect") { |
| | | acc[cur] = query[cur] |
| | | } |
| | | return acc |
| | | }, {}) |
| | | router.push({ path: redirect.value || "/", query: otherQueryParams }) |
| | | const query = route.query; |
| | | const otherQueryParams = Object.keys(query).reduce((acc, cur) => { |
| | | if (cur !== "redirect") { |
| | | acc[cur] = query[cur]; |
| | | } |
| | | return acc; |
| | | }, {}); |
| | | router.push({ path: redirect.value || "/", query: otherQueryParams }); |
| | | }) |
| | | .catch(() => {}) |
| | | .finally(() => { |
| | | loading.value = false |
| | | }) |
| | | } |
| | | loading.value = false; |
| | | }); |
| | | } |
| | | |
| | | getCookie() |
| | | getCookie(); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | * { |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .login-page { |
| | | --blue: #1f78ff; |
| | | --cyan: #20e0d2; |
| | | --violet: #8b5cf6; |
| | | --orange: #ff8a3d; |
| | | --text: #09203f; |
| | | --muted: #7a8da8; |
| | | --line: rgba(113, 154, 214, 0.24); |
| | | --shadow: 0 28px 80px rgba(14, 57, 120, 0.18); |
| | | min-height: 100vh; |
| | | overflow: hidden; |
| | | background: |
| | | radial-gradient(circle at 12% 16%, rgba(31, 120, 255, 0.22), transparent 32%), |
| | | radial-gradient(circle at 84% 14%, rgba(32, 224, 210, 0.24), transparent 28%), |
| | | radial-gradient(circle at 80% 84%, rgba(139, 92, 246, 0.14), transparent 28%), |
| | | linear-gradient(135deg, #eef6ff 0%, #f7fbff 52%, #edf8ff 100%); |
| | | } |
| | | |
| | | .login-page::before { |
| | | content: ""; |
| | | position: fixed; |
| | | inset: 0; |
| | | background-image: |
| | | linear-gradient(rgba(22, 78, 160, 0.055) 1px, transparent 1px), |
| | | linear-gradient(90deg, rgba(22, 78, 160, 0.055) 1px, transparent 1px); |
| | | background-size: 36px 36px; |
| | | mask-image: radial-gradient(circle at center, #000 0%, transparent 78%); |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .page { |
| | | position: relative; |
| | | display: grid; |
| | | grid-template-columns: 1.15fr 0.85fr; |
| | | gap: 28px; |
| | | min-height: 100vh; |
| | | padding: 36px; |
| | | } |
| | | |
| | | .factory { |
| | | position: relative; |
| | | overflow: hidden; |
| | | min-height: calc(100vh - 72px); |
| | | border-radius: 30px; |
| | | padding: 32px; |
| | | color: #fff; |
| | | background: |
| | | linear-gradient(135deg, rgba(5, 27, 67, 0.98), rgba(9, 71, 143, 0.92)), |
| | | radial-gradient(circle at 76% 18%, rgba(32, 224, 210, 0.38), transparent 30%); |
| | | box-shadow: var(--shadow); |
| | | border: 1px solid rgba(255, 255, 255, 0.16); |
| | | animation: enter 0.8s ease both; |
| | | } |
| | | |
| | | .factory::before { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: 0; |
| | | background: |
| | | linear-gradient(120deg, transparent 0%, rgba(255, 255, 255, 0.11) 46%, transparent 72%), |
| | | linear-gradient(rgba(255, 255, 255, 0.055) 1px, transparent 1px), |
| | | linear-gradient(90deg, rgba(255, 255, 255, 0.055) 1px, transparent 1px); |
| | | background-size: 100% 100%, 44px 44px, 44px 44px; |
| | | opacity: 0.86; |
| | | } |
| | | |
| | | .brand { |
| | | position: relative; |
| | | z-index: 2; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .logo { |
| | | width: 76px; |
| | | height: 76px; |
| | | border-radius: 14px; |
| | | display: grid; |
| | | place-items: center; |
| | | background: linear-gradient(135deg, var(--blue), var(--cyan)); |
| | | box-shadow: 0 16px 40px rgba(32, 224, 210, 0.26); |
| | | overflow: hidden; |
| | | position: relative; |
| | | } |
| | | |
| | | .logo-image { |
| | | width: 82%; |
| | | height: 82%; |
| | | object-fit: contain; |
| | | } |
| | | |
| | | .hero-brand { |
| | | display: block; |
| | | } |
| | | |
| | | .hero-logo { |
| | | width: 240px; |
| | | height: 84px; |
| | | border-radius: 0; |
| | | background: transparent; |
| | | box-shadow: none; |
| | | overflow: visible; |
| | | place-items: center start; |
| | | } |
| | | |
| | | .hero-logo-image { |
| | | width: 100%; |
| | | height: auto; |
| | | max-height: 100%; |
| | | object-fit: contain; |
| | | } |
| | | |
| | | .hero { |
| | | position: relative; |
| | | z-index: 2; |
| | | margin-top: 64px; |
| | | max-width: 680px; |
| | | } |
| | | |
| | | .chip { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | padding: 8px 14px; |
| | | border-radius: 999px; |
| | | border: 1px solid rgba(255, 255, 255, 0.18); |
| | | background: rgba(255, 255, 255, 0.1); |
| | | color: rgba(255, 255, 255, 0.78); |
| | | font-size: 13px; |
| | | animation: enter 0.75s ease 0.15s both; |
| | | } |
| | | |
| | | .hero h1 { |
| | | margin: 24px 0 14px; |
| | | font-size: clamp(42px, 5.6vw, 78px); |
| | | line-height: 1.02; |
| | | font-weight: 900; |
| | | letter-spacing: 0; |
| | | animation: enter 0.8s ease 0.25s both; |
| | | } |
| | | |
| | | .hero p { |
| | | margin: 0; |
| | | max-width: 630px; |
| | | font-size: 17px; |
| | | line-height: 1.8; |
| | | color: rgba(255, 255, 255, 0.72); |
| | | animation: enter 0.8s ease 0.35s both; |
| | | } |
| | | |
| | | .scene { |
| | | position: absolute; |
| | | left: 42px; |
| | | right: 42px; |
| | | bottom: 24px; |
| | | height: 44%; |
| | | z-index: 1; |
| | | } |
| | | |
| | | .floor { |
| | | position: absolute; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | height: 48%; |
| | | border-radius: 28px; |
| | | background: |
| | | linear-gradient(90deg, rgba(32, 224, 210, 0), rgba(32, 224, 210, 0.22), rgba(31, 120, 255, 0)), |
| | | repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.1) 0 1px, transparent 1px 64px), |
| | | repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.1) 0 1px, transparent 1px 34px); |
| | | transform: perspective(620px) rotateX(58deg); |
| | | transform-origin: bottom; |
| | | opacity: 0.82; |
| | | } |
| | | |
| | | .factory-svg { |
| | | position: absolute; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 18%; |
| | | width: 100%; |
| | | height: 78%; |
| | | filter: drop-shadow(0 28px 30px rgba(0, 0, 0, 0.18)); |
| | | } |
| | | |
| | | .belt { |
| | | stroke-dasharray: 10 12; |
| | | animation: flow 1.2s linear infinite; |
| | | } |
| | | |
| | | .arm { |
| | | transform-origin: 612px 124px; |
| | | animation: armMove 3.6s ease-in-out infinite; |
| | | } |
| | | |
| | | .box { |
| | | animation: boxMove 5.4s linear infinite; |
| | | } |
| | | |
| | | .box.two { |
| | | animation-delay: -1.8s; |
| | | } |
| | | |
| | | .box.three { |
| | | animation-delay: -3.6s; |
| | | } |
| | | |
| | | .signal { |
| | | opacity: 0.8; |
| | | animation: pulse 2.2s ease-in-out infinite; |
| | | } |
| | | |
| | | .signal.two { |
| | | animation-delay: -0.7s; |
| | | } |
| | | |
| | | .signal.three { |
| | | animation-delay: -1.4s; |
| | | } |
| | | |
| | | .login-wrap { |
| | | position: relative; |
| | | z-index: 2; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | min-height: calc(100vh - 72px); |
| | | border-radius: 30px; |
| | | border: 1px solid rgba(255, 255, 255, 0.8); |
| | | background: rgba(255, 255, 255, 0.62); |
| | | backdrop-filter: blur(20px); |
| | | box-shadow: var(--shadow); |
| | | animation: enter 0.8s ease 0.12s both; |
| | | } |
| | | |
| | | .time { |
| | | position: absolute; |
| | | top: 26px; |
| | | right: 28px; |
| | | display: flex; |
| | | gap: 9px; |
| | | align-items: center; |
| | | font-weight: 900; |
| | | color: #12325e; |
| | | } |
| | | |
| | | .time span { |
| | | padding: 8px 12px; |
| | | border-radius: 999px; |
| | | background: rgba(255, 255, 255, 0.66); |
| | | border: 1px solid var(--line); |
| | | color: var(--muted); |
| | | font-size: 13px; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .login-card { |
| | | width: min(440px, 100%); |
| | | padding: 34px; |
| | | border-radius: 28px; |
| | | border: 1px solid rgba(255, 255, 255, 0.95); |
| | | background: rgba(255, 255, 255, 0.88); |
| | | box-shadow: 0 22px 60px rgba(21, 73, 143, 0.15); |
| | | position: relative; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .login-card::before { |
| | | content: ""; |
| | | position: absolute; |
| | | right: -92px; |
| | | top: -92px; |
| | | width: 190px; |
| | | height: 190px; |
| | | border-radius: 999px; |
| | | background: conic-gradient(from 0deg, rgba(31, 120, 255, 0.2), rgba(32, 224, 210, 0.32), rgba(139, 92, 246, 0.18), rgba(31, 120, 255, 0.2)); |
| | | animation: rotate 9s linear infinite; |
| | | } |
| | | |
| | | .login-card > * { |
| | | position: relative; |
| | | z-index: 1; |
| | | } |
| | | |
| | | .card-brand { |
| | | margin-bottom: 18px; |
| | | } |
| | | |
| | | .card-brand .logo { |
| | | width: 52px; |
| | | height: 52px; |
| | | } |
| | | |
| | | .card-brand-copy { |
| | | margin-left: 12px; |
| | | } |
| | | |
| | | .card-logo-image { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: contain; |
| | | } |
| | | |
| | | .card-brand .logo { |
| | | width: 68px; |
| | | height: 68px; |
| | | } |
| | | |
| | | .login-card h2 { |
| | | margin: 8px 0; |
| | | font-size: 31px; |
| | | line-height: 1.15; |
| | | color: #0d2c5e; |
| | | font-weight: 900; |
| | | letter-spacing: 0; |
| | | } |
| | | |
| | | .sub { |
| | | margin: 0 0 24px; |
| | | color: var(--muted); |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .form-row { |
| | | margin-bottom: 14px; |
| | | animation: enter 0.6s ease both; |
| | | } |
| | | |
| | | .form-row:nth-of-type(1) { |
| | | animation-delay: 0.32s; |
| | | } |
| | | |
| | | .form-row:nth-of-type(2) { |
| | | animation-delay: 0.4s; |
| | | } |
| | | |
| | | .form-row:nth-of-type(3) { |
| | | animation-delay: 0.48s; |
| | | } |
| | | |
| | | label { |
| | | display: block; |
| | | margin-bottom: 8px; |
| | | color: #24436b; |
| | | font-size: 13px; |
| | | font-weight: 800; |
| | | } |
| | | |
| | | .input { |
| | | position: relative; |
| | | } |
| | | |
| | | .input svg { |
| | | position: absolute; |
| | | left: 15px; |
| | | top: 50%; |
| | | width: 19px; |
| | | height: 19px; |
| | | transform: translateY(-50%); |
| | | color: #8197b6; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | input[type="text"], |
| | | input[type="password"] { |
| | | width: 100%; |
| | | height: 52px; |
| | | padding: 0 15px 0 46px; |
| | | border: 1px solid rgba(108, 143, 190, 0.34); |
| | | border-radius: 16px; |
| | | outline: none; |
| | | background: linear-gradient(180deg, #fff, #f8fbff); |
| | | color: var(--text); |
| | | font-size: 15px; |
| | | transition: 0.2s ease; |
| | | } |
| | | |
| | | input[type="text"]:hover, |
| | | input[type="password"]:hover { |
| | | border-color: rgba(31, 120, 255, 0.48); |
| | | box-shadow: 0 10px 24px rgba(31, 120, 255, 0.08); |
| | | } |
| | | |
| | | input[type="text"]:focus, |
| | | input[type="password"]:focus { |
| | | border-color: rgba(31, 120, 255, 0.72); |
| | | box-shadow: 0 0 0 4px rgba(31, 120, 255, 0.1); |
| | | } |
| | | |
| | | .options { |
| | | display: flex; |
| | | justify-content: flex-start; |
| | | align-items: center; |
| | | margin: 12px 0 22px; |
| | | font-size: 13px; |
| | | color: var(--muted); |
| | | } |
| | | |
| | | .check { |
| | | display: inline-flex; |
| | | gap: 8px; |
| | | align-items: center; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .check input { |
| | | width: 16px; |
| | | height: 16px; |
| | | padding: 0; |
| | | accent-color: var(--blue); |
| | | } |
| | | |
| | | .login-btn { |
| | | width: 100%; |
| | | height: 54px; |
| | | border: none; |
| | | border-radius: 17px; |
| | | cursor: pointer; |
| | | color: #fff; |
| | | font-size: 16px; |
| | | font-weight: 900; |
| | | background: linear-gradient(135deg, var(--blue), var(--cyan)); |
| | | box-shadow: 0 18px 38px rgba(31, 120, 255, 0.25); |
| | | position: relative; |
| | | overflow: hidden; |
| | | transition: 0.18s ease; |
| | | } |
| | | |
| | | .login-btn:hover:not(:disabled) { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 22px 48px rgba(31, 120, 255, 0.32); |
| | | } |
| | | |
| | | .login-btn:disabled { |
| | | opacity: 0.8; |
| | | cursor: not-allowed; |
| | | } |
| | | |
| | | @keyframes enter { |
| | | from { |
| | | opacity: 0; |
| | | transform: translateY(26px); |
| | | } |
| | | to { |
| | | opacity: 1; |
| | | transform: translateY(0); |
| | | } |
| | | } |
| | | |
| | | @keyframes rotate { |
| | | to { |
| | | transform: rotate(360deg); |
| | | } |
| | | } |
| | | |
| | | @keyframes flow { |
| | | to { |
| | | stroke-dashoffset: -44; |
| | | } |
| | | } |
| | | |
| | | @keyframes boxMove { |
| | | 0% { |
| | | transform: translateX(-150px); |
| | | opacity: 0; |
| | | } |
| | | 12% { |
| | | opacity: 1; |
| | | } |
| | | 78% { |
| | | opacity: 1; |
| | | } |
| | | 100% { |
| | | transform: translateX(650px); |
| | | opacity: 0; |
| | | } |
| | | } |
| | | |
| | | @keyframes armMove { |
| | | 0%, |
| | | 100% { |
| | | transform: rotate(-4deg); |
| | | } |
| | | 50% { |
| | | transform: rotate(9deg); |
| | | } |
| | | } |
| | | |
| | | @keyframes pulse { |
| | | 0%, |
| | | 100% { |
| | | opacity: 0.45; |
| | | transform: scale(1); |
| | | } |
| | | 50% { |
| | | opacity: 1; |
| | | transform: scale(1.18); |
| | | } |
| | | } |
| | | |
| | | @keyframes glow { |
| | | 0%, |
| | | 100% { |
| | | box-shadow: 0 0 18px rgba(32, 224, 210, 0.3); |
| | | } |
| | | 50% { |
| | | box-shadow: 0 0 34px rgba(32, 224, 210, 0.68); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 1320px) { |
| | | .hero h1 { |
| | | font-size: clamp(52px, 5.2vw, 78px); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 980px) { |
| | | .login-page { |
| | | overflow: auto; |
| | | --accent: var(--accent-primary, var(--el-color-primary, #374d77)); |
| | | --accent-rgb: var(--el-color-primary-rgb, 22, 116, 88); |
| | | --text: #0f172a; |
| | | --muted: #64748b; |
| | | --border: rgba(15, 23, 42, 0.1); |
| | | --shadow: 0 24px 70px rgba(2, 6, 23, 0.12); |
| | | position: relative; |
| | | min-height: 100vh; |
| | | overflow: hidden; |
| | | background: radial-gradient( |
| | | circle at 12% 14%, |
| | | rgba(var(--accent-rgb), 0.14), |
| | | transparent 38% |
| | | ), |
| | | radial-gradient(circle at 92% 8%, rgba(2, 132, 199, 0.12), transparent 36%), |
| | | conic-gradient( |
| | | from 220deg at 24% 18%, |
| | | rgba(var(--accent-rgb), 0.1), |
| | | rgba(56, 189, 248, 0.08), |
| | | rgba(99, 102, 241, 0.08), |
| | | rgba(var(--accent-rgb), 0.1) |
| | | ), |
| | | linear-gradient(180deg, #f7f8fb 0%, #f3f5fa 100%); |
| | | } |
| | | |
| | | .login-page * { |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .login-page::before { |
| | | content: ""; |
| | | position: fixed; |
| | | inset: 0; |
| | | background-image: radial-gradient(rgba(15, 23, 42, 0.05) 1px, transparent 1px), |
| | | linear-gradient(rgba(15, 23, 42, 0.045) 1px, transparent 1px), |
| | | linear-gradient(90deg, rgba(15, 23, 42, 0.045) 1px, transparent 1px), |
| | | repeating-linear-gradient( |
| | | 118deg, |
| | | transparent 0 84px, |
| | | rgba(var(--accent-rgb), 0.26) 84px 86px, |
| | | transparent 86px 168px |
| | | ), |
| | | repeating-linear-gradient( |
| | | 208deg, |
| | | transparent 0 132px, |
| | | rgba(2, 132, 199, 0.22) 132px 134px, |
| | | transparent 134px 264px |
| | | ), |
| | | repeating-linear-gradient( |
| | | 156deg, |
| | | transparent 0 112px, |
| | | rgba(99, 102, 241, 0.2) 112px 114px, |
| | | transparent 114px 224px |
| | | ), |
| | | repeating-linear-gradient( |
| | | 118deg, |
| | | transparent 0 84px, |
| | | rgba(var(--accent-rgb), 0.12) 83px 89px, |
| | | transparent 89px 168px |
| | | ); |
| | | background-size: 22px 22px, 52px 52px, 52px 52px, auto, auto, auto, auto; |
| | | background-position: 0 0, 0 0, 0 0, 0 0, -120px 140px, 120px 0; |
| | | mask-image: radial-gradient(circle at 42% 42%, #000 0%, transparent 80%); |
| | | opacity: 1; |
| | | mix-blend-mode: normal; |
| | | filter: saturate(1.08); |
| | | animation: lineDrift 12s linear infinite; |
| | | pointer-events: none; |
| | | z-index: 0; |
| | | } |
| | | |
| | | .login-page::after { |
| | | content: ""; |
| | | position: fixed; |
| | | inset: -28vmax; |
| | | background: radial-gradient( |
| | | circle at 18% 28%, |
| | | rgba(var(--accent-rgb), 0.22), |
| | | transparent 54% |
| | | ), |
| | | radial-gradient( |
| | | circle at 82% 22%, |
| | | rgba(56, 189, 248, 0.16), |
| | | transparent 50% |
| | | ), |
| | | radial-gradient( |
| | | circle at 72% 82%, |
| | | rgba(99, 102, 241, 0.12), |
| | | transparent 52% |
| | | ), |
| | | radial-gradient(circle at 26% 78%, rgba(244, 63, 94, 0.06), transparent 56%); |
| | | filter: blur(42px) saturate(1.05); |
| | | opacity: 0.9; |
| | | transform: translate3d(0, 0, 0); |
| | | animation: bgFloat 18s ease-in-out infinite; |
| | | pointer-events: none; |
| | | z-index: 0; |
| | | } |
| | | |
| | | .page { |
| | | grid-template-columns: 1fr; |
| | | padding: 18px; |
| | | min-height: auto; |
| | | position: relative; |
| | | z-index: 1; |
| | | max-width: 1200px; |
| | | margin: 48px auto; |
| | | width: calc(100% - 48px); |
| | | min-height: calc(100vh - 96px); |
| | | display: grid; |
| | | grid-template-columns: 3fr 2fr; |
| | | gap: 0; |
| | | align-items: stretch; |
| | | border-radius: 34px; |
| | | background: rgba(255, 255, 255, 0.72); |
| | | border: 1px solid rgba(255, 255, 255, 0.72); |
| | | box-shadow: var(--shadow); |
| | | backdrop-filter: blur(18px); |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .factory, |
| | | .login-wrap { |
| | | min-height: auto; |
| | | .page::before { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: 0; |
| | | border-radius: inherit; |
| | | background: radial-gradient( |
| | | circle at 22% 22%, |
| | | rgba(var(--accent-rgb), 0.34), |
| | | transparent 58% |
| | | ) |
| | | left center / 60% 100% no-repeat, |
| | | radial-gradient( |
| | | circle at 76% 12%, |
| | | rgba(56, 189, 248, 0.22), |
| | | transparent 50% |
| | | ) |
| | | left center / 60% 100% no-repeat, |
| | | linear-gradient( |
| | | 135deg, |
| | | rgba(2, 6, 23, 0.96) 0%, |
| | | rgba(15, 35, 57, 0.94) 52%, |
| | | rgba(15, 35, 57, 0.88) 100% |
| | | ) |
| | | left center / 60% 100% no-repeat; |
| | | pointer-events: none; |
| | | z-index: 0; |
| | | } |
| | | |
| | | .page::after { |
| | | content: ""; |
| | | position: absolute; |
| | | top: 0; |
| | | bottom: 0; |
| | | left: 60%; |
| | | width: 1px; |
| | | background: linear-gradient( |
| | | 180deg, |
| | | transparent, |
| | | rgba(15, 23, 42, 0.16), |
| | | transparent |
| | | ); |
| | | pointer-events: none; |
| | | z-index: 0; |
| | | } |
| | | |
| | | .factory { |
| | | min-height: 760px; |
| | | position: relative; |
| | | z-index: 1; |
| | | padding: 44px 48px 40px; |
| | | color: rgba(255, 255, 255, 0.92); |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .time { |
| | | .brand { |
| | | position: relative; |
| | | z-index: 1; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .logo { |
| | | width: 52px; |
| | | height: 52px; |
| | | border-radius: 14px; |
| | | display: grid; |
| | | place-items: center; |
| | | background: rgba(var(--accent-rgb), 0.12); |
| | | border: 1px solid rgba(var(--accent-rgb), 0.22); |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .logo-image { |
| | | width: 80%; |
| | | height: 80%; |
| | | object-fit: contain; |
| | | } |
| | | |
| | | .hero-brand { |
| | | display: block; |
| | | } |
| | | |
| | | .hero-logo { |
| | | width: 260px; |
| | | height: 72px; |
| | | border-radius: 0; |
| | | background: transparent; |
| | | border: none; |
| | | place-items: center start; |
| | | } |
| | | |
| | | .hero-logo-image { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: contain; |
| | | filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.22)); |
| | | } |
| | | |
| | | .hero { |
| | | position: relative; |
| | | z-index: 1; |
| | | margin: 46px 0 0; |
| | | max-width: 640px; |
| | | } |
| | | |
| | | .chip { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | padding: 7px 12px; |
| | | border-radius: 999px; |
| | | border: 1px solid rgba(255, 255, 255, 0.16); |
| | | background: rgba(255, 255, 255, 0.08); |
| | | color: rgba(255, 255, 255, 0.78); |
| | | font-size: 13px; |
| | | letter-spacing: 0.04em; |
| | | text-transform: uppercase; |
| | | } |
| | | |
| | | .hero h1 { |
| | | margin: 22px 0 14px; |
| | | font-size: clamp(44px, 5vw, 66px); |
| | | line-height: 1.06; |
| | | font-weight: 860; |
| | | letter-spacing: -0.02em; |
| | | color: #ffffff; |
| | | } |
| | | |
| | | .hero p { |
| | | margin: 0; |
| | | max-width: 600px; |
| | | font-size: 16px; |
| | | line-height: 1.9; |
| | | color: rgba(255, 255, 255, 0.72); |
| | | } |
| | | |
| | | .scene { |
| | | display: none; |
| | | } |
| | | |
| | | .login-wrap { |
| | | padding: 22px; |
| | | position: relative; |
| | | z-index: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 36px 44px; |
| | | } |
| | | |
| | | .time { |
| | | position: absolute; |
| | | top: 18px; |
| | | right: 18px; |
| | | display: flex; |
| | | gap: 10px; |
| | | align-items: center; |
| | | color: rgba(15, 23, 42, 0.72); |
| | | font-size: 13px; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .time span { |
| | | padding: 6px 10px; |
| | | border-radius: 999px; |
| | | border: 1px solid rgba(15, 23, 42, 0.12); |
| | | background: rgba(255, 255, 255, 0.7); |
| | | color: rgba(15, 23, 42, 0.6); |
| | | font-weight: 800; |
| | | } |
| | | |
| | | .login-card { |
| | | width: min(420px, 100%); |
| | | padding: 38px 34px 34px; |
| | | border-radius: 22px; |
| | | border: 1px solid rgba(15, 23, 42, 0.12); |
| | | background: rgba(255, 255, 255, 0.86); |
| | | backdrop-filter: blur(14px); |
| | | box-shadow: var(--shadow); |
| | | position: relative; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .login-card::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: -120px; |
| | | top: -120px; |
| | | width: 240px; |
| | | height: 240px; |
| | | border-radius: 999px; |
| | | background: radial-gradient( |
| | | circle at 30% 30%, |
| | | rgba(var(--accent-rgb), 0.36), |
| | | transparent 60% |
| | | ); |
| | | filter: blur(1px); |
| | | } |
| | | |
| | | .login-card > * { |
| | | position: relative; |
| | | z-index: 1; |
| | | } |
| | | |
| | | .card-brand { |
| | | margin-bottom: 18px; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .card-brand .logo { |
| | | width: 46px; |
| | | height: 46px; |
| | | border-radius: 14px; |
| | | background: rgba(var(--accent-rgb), 0.12); |
| | | border: 1px solid rgba(var(--accent-rgb), 0.22); |
| | | } |
| | | |
| | | .card-brand-copy { |
| | | margin-left: 0; |
| | | } |
| | | |
| | | .brand-title { |
| | | font-size: 15px; |
| | | font-weight: 900; |
| | | color: rgba(15, 23, 42, 0.92); |
| | | letter-spacing: 0.02em; |
| | | } |
| | | |
| | | .card-brand-copy small { |
| | | display: block; |
| | | margin-top: 4px; |
| | | font-size: 12px; |
| | | color: rgba(15, 23, 42, 0.55); |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .login-card h2 { |
| | | margin: 10px 0 8px; |
| | | font-size: 28px; |
| | | line-height: 1.2; |
| | | color: rgba(15, 23, 42, 0.92); |
| | | font-weight: 900; |
| | | letter-spacing: -0.02em; |
| | | } |
| | | |
| | | .sub { |
| | | margin: 0 0 22px; |
| | | color: var(--muted); |
| | | font-size: 13px; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .form-row { |
| | | margin-bottom: 14px; |
| | | } |
| | | |
| | | label { |
| | | display: block; |
| | | margin-bottom: 8px; |
| | | color: rgba(15, 23, 42, 0.72); |
| | | font-size: 12px; |
| | | font-weight: 800; |
| | | letter-spacing: 0.06em; |
| | | text-transform: uppercase; |
| | | } |
| | | |
| | | .input { |
| | | position: relative; |
| | | } |
| | | |
| | | .input svg { |
| | | position: absolute; |
| | | left: 14px; |
| | | top: 50%; |
| | | width: 18px; |
| | | height: 18px; |
| | | transform: translateY(-50%); |
| | | color: rgba(15, 23, 42, 0.45); |
| | | pointer-events: none; |
| | | } |
| | | |
| | | input[type="text"], |
| | | input[type="password"] { |
| | | width: 100%; |
| | | padding: 26px; |
| | | height: 50px; |
| | | padding: 0 14px 0 44px; |
| | | border: 1px solid var(--border); |
| | | border-radius: 14px; |
| | | outline: none; |
| | | background: rgba(255, 255, 255, 0.92); |
| | | color: var(--text); |
| | | font-size: 15px; |
| | | transition: box-shadow 0.16s ease, border-color 0.16s ease, |
| | | transform 0.16s ease; |
| | | } |
| | | |
| | | input[type="text"]::placeholder, |
| | | input[type="password"]::placeholder { |
| | | color: rgba(100, 116, 139, 0.88); |
| | | } |
| | | |
| | | input[type="text"]:hover, |
| | | input[type="password"]:hover { |
| | | border-color: rgba(var(--accent-rgb), 0.28); |
| | | } |
| | | |
| | | input[type="text"]:focus, |
| | | input[type="password"]:focus { |
| | | border-color: rgba(var(--accent-rgb), 0.6); |
| | | box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.14); |
| | | } |
| | | |
| | | .options { |
| | | display: flex; |
| | | justify-content: flex-start; |
| | | align-items: center; |
| | | margin: 14px 0 22px; |
| | | font-size: 13px; |
| | | color: rgba(15, 23, 42, 0.62); |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .check { |
| | | display: inline-flex; |
| | | gap: 10px; |
| | | align-items: center; |
| | | cursor: pointer; |
| | | user-select: none; |
| | | } |
| | | |
| | | .check input { |
| | | width: 16px; |
| | | height: 16px; |
| | | padding: 0; |
| | | accent-color: var(--accent); |
| | | } |
| | | |
| | | .login-btn { |
| | | font-size: 16px; |
| | | width: 100%; |
| | | height: 52px; |
| | | border: none; |
| | | border-radius: 14px; |
| | | cursor: pointer; |
| | | color: #ffffff; |
| | | font-size: 15px; |
| | | font-weight: 900; |
| | | letter-spacing: 0.02em; |
| | | background: var(--accent); |
| | | box-shadow: 0 18px 44px rgba(var(--accent-rgb), 0.22); |
| | | transition: transform 0.16s ease, box-shadow 0.16s ease, filter 0.16s ease; |
| | | } |
| | | |
| | | } |
| | | .login-btn:hover:not(:disabled) { |
| | | transform: translateY(-1px); |
| | | box-shadow: 0 20px 52px rgba(var(--accent-rgb), 0.28); |
| | | filter: saturate(1.02); |
| | | } |
| | | |
| | | .login-btn:active:not(:disabled) { |
| | | transform: translateY(0); |
| | | } |
| | | |
| | | .login-btn:disabled { |
| | | opacity: 0.72; |
| | | cursor: not-allowed; |
| | | } |
| | | |
| | | .login-btn:focus-visible { |
| | | outline: none; |
| | | box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.18), |
| | | 0 18px 44px rgba(var(--accent-rgb), 0.22); |
| | | } |
| | | |
| | | :global(html.dark) .login-page { |
| | | --text: rgba(248, 250, 252, 0.92); |
| | | --muted: rgba(226, 232, 240, 0.62); |
| | | --border: rgba(226, 232, 240, 0.16); |
| | | background: radial-gradient( |
| | | circle at 14% 18%, |
| | | rgba(var(--accent-rgb), 0.22), |
| | | transparent 40% |
| | | ), |
| | | radial-gradient( |
| | | circle at 86% 12%, |
| | | rgba(56, 189, 248, 0.14), |
| | | transparent 42% |
| | | ), |
| | | conic-gradient( |
| | | from 220deg at 22% 16%, |
| | | rgba(var(--accent-rgb), 0.16), |
| | | rgba(56, 189, 248, 0.12), |
| | | rgba(99, 102, 241, 0.12), |
| | | rgba(var(--accent-rgb), 0.16) |
| | | ), |
| | | linear-gradient(180deg, #0b1220 0%, #070b13 100%); |
| | | } |
| | | |
| | | :global(html.dark) .login-page::before { |
| | | background-image: radial-gradient( |
| | | rgba(226, 232, 240, 0.07) 1px, |
| | | transparent 1px |
| | | ), |
| | | linear-gradient(rgba(226, 232, 240, 0.06) 1px, transparent 1px), |
| | | linear-gradient(90deg, rgba(226, 232, 240, 0.06) 1px, transparent 1px), |
| | | repeating-linear-gradient( |
| | | 118deg, |
| | | transparent 0 84px, |
| | | rgba(var(--accent-rgb), 0.32) 84px 86px, |
| | | transparent 86px 168px |
| | | ), |
| | | repeating-linear-gradient( |
| | | 208deg, |
| | | transparent 0 132px, |
| | | rgba(56, 189, 248, 0.28) 132px 134px, |
| | | transparent 134px 264px |
| | | ), |
| | | repeating-linear-gradient( |
| | | 156deg, |
| | | transparent 0 112px, |
| | | rgba(99, 102, 241, 0.26) 112px 114px, |
| | | transparent 114px 224px |
| | | ), |
| | | repeating-linear-gradient( |
| | | 118deg, |
| | | transparent 0 84px, |
| | | rgba(var(--accent-rgb), 0.16) 83px 89px, |
| | | transparent 89px 168px |
| | | ); |
| | | background-size: 22px 22px, 52px 52px, 52px 52px, auto, auto, auto, auto; |
| | | opacity: 0.96; |
| | | mix-blend-mode: screen; |
| | | } |
| | | |
| | | :global(html.dark) .login-page::after { |
| | | opacity: 0.7; |
| | | filter: blur(46px) saturate(1.08); |
| | | background: radial-gradient( |
| | | circle at 18% 28%, |
| | | rgba(var(--accent-rgb), 0.28), |
| | | transparent 56% |
| | | ), |
| | | radial-gradient( |
| | | circle at 84% 18%, |
| | | rgba(56, 189, 248, 0.18), |
| | | transparent 52% |
| | | ), |
| | | radial-gradient( |
| | | circle at 70% 82%, |
| | | rgba(99, 102, 241, 0.14), |
| | | transparent 56% |
| | | ), |
| | | radial-gradient(circle at 30% 84%, rgba(244, 63, 94, 0.06), transparent 60%); |
| | | } |
| | | |
| | | :global(html.dark) .time { |
| | | color: rgba(226, 232, 240, 0.68); |
| | | } |
| | | |
| | | :global(html.dark) .time span { |
| | | background: rgba(15, 23, 42, 0.5); |
| | | border-color: rgba(226, 232, 240, 0.16); |
| | | color: rgba(226, 232, 240, 0.72); |
| | | } |
| | | |
| | | :global(html.dark) .login-card { |
| | | background: rgba(2, 6, 23, 0.56); |
| | | border-color: rgba(226, 232, 240, 0.16); |
| | | box-shadow: 0 24px 70px rgba(0, 0, 0, 0.46); |
| | | } |
| | | |
| | | :global(html.dark) .page { |
| | | background: rgba(2, 6, 23, 0.46); |
| | | border-color: rgba(226, 232, 240, 0.12); |
| | | box-shadow: 0 38px 96px rgba(0, 0, 0, 0.5); |
| | | } |
| | | |
| | | :global(html.dark) .page::after { |
| | | background: linear-gradient( |
| | | 180deg, |
| | | transparent, |
| | | rgba(226, 232, 240, 0.14), |
| | | transparent |
| | | ); |
| | | } |
| | | |
| | | :global(html.dark) .brand-title { |
| | | color: rgba(248, 250, 252, 0.92); |
| | | } |
| | | |
| | | :global(html.dark) .card-brand-copy small { |
| | | color: rgba(226, 232, 240, 0.62); |
| | | } |
| | | |
| | | :global(html.dark) .login-card h2 { |
| | | color: rgba(248, 250, 252, 0.92); |
| | | } |
| | | |
| | | :global(html.dark) label { |
| | | color: rgba(226, 232, 240, 0.72); |
| | | } |
| | | |
| | | :global(html.dark) .input svg { |
| | | color: rgba(226, 232, 240, 0.55); |
| | | } |
| | | |
| | | :global(html.dark) input[type="text"], |
| | | :global(html.dark) input[type="password"] { |
| | | background: rgba(2, 6, 23, 0.38); |
| | | color: rgba(248, 250, 252, 0.92); |
| | | } |
| | | |
| | | :global(html.dark) input[type="text"]::placeholder, |
| | | :global(html.dark) input[type="password"]::placeholder { |
| | | color: rgba(226, 232, 240, 0.55); |
| | | } |
| | | |
| | | :global(html.dark) .options { |
| | | color: rgba(226, 232, 240, 0.68); |
| | | } |
| | | |
| | | @keyframes bgFloat { |
| | | 0% { |
| | | transform: translate3d(-1.8%, -1.2%, 0) scale(1.02); |
| | | } |
| | | 50% { |
| | | transform: translate3d(1.6%, 1.4%, 0) scale(1.06); |
| | | } |
| | | 100% { |
| | | transform: translate3d(-1.8%, -1.2%, 0) scale(1.02); |
| | | } |
| | | } |
| | | |
| | | @keyframes lineDrift { |
| | | 0% { |
| | | background-position: 0 0, 0 0, 0 0, 0 0, -120px 140px, 120px 0, 0 0; |
| | | } |
| | | 100% { |
| | | background-position: 0 0, 220px 260px, -240px 210px, 760px -520px, |
| | | -980px 720px, 680px 860px, 760px -520px; |
| | | } |
| | | } |
| | | |
| | | @media (prefers-reduced-motion: reduce) { |
| | | .login-page::before { |
| | | animation: none; |
| | | } |
| | | |
| | | .login-page::after { |
| | | animation: none; |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 980px) { |
| | | .login-page { |
| | | overflow: auto; |
| | | } |
| | | |
| | | .page { |
| | | grid-template-columns: 1fr; |
| | | gap: 18px; |
| | | margin: 18px auto; |
| | | width: calc(100% - 32px); |
| | | min-height: auto; |
| | | max-width: 560px; |
| | | } |
| | | |
| | | .page::before, |
| | | .page::after { |
| | | display: none; |
| | | } |
| | | |
| | | .factory { |
| | | display: none; |
| | | } |
| | | |
| | | .login-wrap { |
| | | padding: 0; |
| | | } |
| | | |
| | | .time { |
| | | display: none; |
| | | } |
| | | |
| | | .login-card { |
| | | width: 100%; |
| | | padding: 30px 22px 22px; |
| | | } |
| | | } |
| | | </style> |