diff --git a/packages/opendesign/src/_utils/is.ts b/packages/opendesign/src/_utils/is.ts index ed9e7fce0de7359e2a1e4ef8835fa79f3e810a3b..e36057f6e993775fec0e585b91bbc0a0576c7d67 100644 --- a/packages/opendesign/src/_utils/is.ts +++ b/packages/opendesign/src/_utils/is.ts @@ -82,3 +82,26 @@ export const isTouchDevice = isClient ? 'ontouchstart' in document.documentEleme export function isWindow(val: unknown): val is Window { return val === window; } + +/** + * 判断一个给定的路径是否是当前页面的内部链接 + * @param {string} link - 需要判断的路径(可以是相对路径、绝对路径或完整URL) + * @returns {boolean} - 如果是外部链接返回true,否则返回false + */ +export function isCurrentPageLink(link: string): boolean { + // 处理锚点或空路径,通常视为内部链接 + if (link.startsWith('#')) { + return true; + } + + try { + // 使用URL对象解析目标路径(以当前页面为基准) + const targetUrl = new URL(link, window.location.href); + + // 比较协议、主机名、端口和路径 + return targetUrl.origin + targetUrl.pathname === window.location.origin + window.location.pathname; + } catch { + // 如果URL解析失败(如不规范的路径),判定为外部链接 + return false; + } +} diff --git a/packages/opendesign/src/anchor/OAnchor.vue b/packages/opendesign/src/anchor/OAnchor.vue index deb53ecf38805a936531de6d68759f588ea204a6..6f385ffb427e239a80e16b127d1c7c517f3dcab8 100644 --- a/packages/opendesign/src/anchor/OAnchor.vue +++ b/packages/opendesign/src/anchor/OAnchor.vue @@ -1,13 +1,16 @@ @@ -51,5 +80,6 @@ import { OAnchor, OAnchorItem } from '../index'; position: fixed; top: 200px; right: 60px; + display: flex; } diff --git a/packages/opendesign/src/anchor/provide.ts b/packages/opendesign/src/anchor/provide.ts index 10a0eccb0b46a357522bd72af00d42f020594f22..ad455de9af626dbeed09df13dc89a16f9bb16d4a 100644 --- a/packages/opendesign/src/anchor/provide.ts +++ b/packages/opendesign/src/anchor/provide.ts @@ -3,8 +3,13 @@ import { InjectionKey, Ref } from 'vue'; export const anchorInjectKey: InjectionKey<{ addLink: (link: string) => void; removeLink: (link: string) => void; - handleClick: (ev: MouseEvent, link?: string) => void; + /** + * @deprecated 兼容旧版本,计划1.2.0移除 + */ + onItemClick: (options: { event: MouseEvent; link?: string }) => void; activeLink: Ref; + scrollIntoView: (link: string) => void; + getChangeHash: () => boolean; }> = Symbol('provide-anchor'); export const anchorItemInjectKey: InjectionKey<{ diff --git a/packages/opendesign/src/anchor/style/index.scss b/packages/opendesign/src/anchor/style/index.scss index 504d59c44eea7243e3faa4e454b7675ea8b98768..8bb47c5840ef5591b0176e882570a9878c439b64 100644 --- a/packages/opendesign/src/anchor/style/index.scss +++ b/packages/opendesign/src/anchor/style/index.scss @@ -1,2 +1,3 @@ @use './style.scss' as *; +@use './var.scss' as *; @use './media.scss' as *; diff --git a/packages/opendesign/src/anchor/style/media.scss b/packages/opendesign/src/anchor/style/media.scss index f415089a72379f537e904f06a5668e095fa1b0f7..d2af976972e4f83ab97cbf556a5d2cbb402a0c66 100644 --- a/packages/opendesign/src/anchor/style/media.scss +++ b/packages/opendesign/src/anchor/style/media.scss @@ -1,8 +1,25 @@ @use '../../_styles/mixin.scss' as *; -@include respond-to('<=laptop') { - .o-anchor-item { - --anchor-item-link-text-size: var(--o-font_size-tip1); - --anchor-item-link-text-height: var(--o-line_height-tip1); +@include respond-to('<=pc_s') { + .o-anchor:not(.o-anchor-menu) { + .o-anchor-item { + --anchor-item-link-text-size: var(--o-font_size-tip1); + --anchor-item-link-text-height: var(--o-line_height-tip1); + + --anchor-item-sub-link-text-size: var(--o-font_size-tip2); + --anchor-item-sub-link-text-height: var(--o-line_height-tip2); + + --anchor-item-link-padding-v: 5px; + --anchor-item-sub-link-padding-v: 5px; + } } } + +@include respond-to('<=pad_v') { + .o-anchor-menu { + .o-anchor-item { + --anchor-item-sub-link-text-size: var(--o-font_size-tip2); + --anchor-item-sub-link-text-height: var(--o-line_height-tip2); + } + } +} \ No newline at end of file diff --git a/packages/opendesign/src/anchor/style/style.scss b/packages/opendesign/src/anchor/style/style.scss index daf4883e26556eaf42cef987e415e9acac5fb7c8..3b61f21e76d2fbd6e52b8db26e10788d64b8bdfc 100644 --- a/packages/opendesign/src/anchor/style/style.scss +++ b/packages/opendesign/src/anchor/style/style.scss @@ -1,5 +1,4 @@ @use '../../_styles/mixin.scss' as *; -@use './var.scss'; .o-anchor { position: relative; @@ -8,11 +7,12 @@ } .o-anchor-line { + flex-shrink: 0; position: relative; width: var(--anchor-line-width); border-radius: var(--anchor-line-width); - background-color: var(--anchor-line-bg-color); - margin-right: 4px; + margin-right: var(--anchor-line-gap); + margin-left: calc(var(--anchor-circle-size) / 2); } .o-anchor-indicator { @@ -22,7 +22,7 @@ width: var(--anchor-indicator-width); background-color: transparent; opacity: 0; - transition: all var(--o-easing-standard-in) var(--o-duration-m1); + transition: top var(--o-easing-standard-in) var(--o-duration-m1); &::after { content: ''; @@ -39,36 +39,121 @@ } .o-anchor-item-link { + --anchor-item-link-left-extra-indent: calc(var(--anchor-item-text-indent) * var(--anchor-item-depth)); + --anchor-circle-top: calc( + var(--anchor-item-link-padding-v) + var(--anchor-item-link-text-height) / 2 - var(--anchor-circle-size) / 2 + var(--anchor-indicator-adjust) + ); cursor: pointer; display: inline-flex; - color: inherit; text-decoration: none; width: 100%; - color: var(--anchor-item-link-color); + color: var(--anchor-item-link-color, inherit); font-size: var(--anchor-item-link-text-size); line-height: var(--anchor-item-link-text-height); - background-color: var(--anchor-item-link-bg-color); - padding: var(--anchor-item-link-padding); + padding: var(--anchor-item-link-padding-v) var(--anchor-item-link-padding-h); + padding-left: calc(var(--anchor-item-link-left-extra-indent) + var(--anchor-item-link-padding-h)); border-radius: var(--anchor-item-link-radius); transition: background-color var(--o-duration-s) var(--o-easing-standard); + word-break: break-word; + position: relative; @include hover { - background-color: var(--anchor-item-link-bg-color-hover); + color: var(--anchor-item-link-color-hover); + } + + &:active { + color: var(--anchor-item-link-color-press); } - &.is-active { - font-weight: 500; + &.disabled { + color: var(--anchor-item-link-color-disabled); + @include hover { + cursor: not-allowed; + } + } +} + +.o-anchor-item:has(.o-anchor-item-link.is-active) > .o-anchor-item-link, +.o-anchor-item-link.is-active { + &:not(.disabled) { + font-weight: 600; color: var(--anchor-item-link-color-active); - background-color: var(--anchor-item-link-bg-color-active); } } -.o-anchor-item + .o-anchor-item { - margin-top: var(--anchor-item-link-gap); +.o-anchor-item-title { + max-height: calc(var(--anchor-item-max-row) * var(--anchor-item-link-text-height)); + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: var(--anchor-item-max-row); } -.o-anchor-item { - .o-anchor-item { - margin-top: var(--anchor-item-link-gap); +.o-anchor-item-sub-link { + font-size: var(--anchor-item-sub-link-text-size); + line-height: var(--anchor-item-sub-link-text-height); + padding-top: var(--anchor-item-sub-link-padding-v); + padding-bottom: var(--anchor-item-sub-link-padding-v); +} + +.o-anchor-item-lines { + width: var(--anchor-line-width); + position: absolute; + top: 0; + bottom: 0; + left: calc(0px - var(--anchor-line-gap) - var(--anchor-line-width)); +} + +.o-anchor-item-top-line, +.o-anchor-item-bottom-line { + background-color: var(--anchor-line-bg-color); + position: absolute; + left: 0; + right: 0; +} + +.o-anchor-item-top-line { + top: 0; + bottom: 50%; +} + +.o-anchor-item-bottom-line { + top: 50%; + bottom: 0; +} + +.o-anchor-item-link:not(.o-anchor-item-sub-link) { + > .o-anchor-item-lines { + .o-anchor-item-circle { + position: absolute; + height: var(--anchor-circle-size); + width: var(--anchor-circle-size); + top: var(--anchor-circle-top); + left: 50%; + transform: translateX(-50%); + border: var(--anchor-circle-ring-width) solid currentColor; + border-radius: 50%; + } + .o-anchor-item-top-line { + bottom: unset; + height: calc(var(--anchor-circle-top) - var(--anchor-circle-gap)); + } + .o-anchor-item-bottom-line { + top: calc(var(--anchor-circle-top) + var(--anchor-circle-size) + var(--anchor-circle-gap)); + } + } +} + +.o-anchor-items > .o-anchor-item { + &:first-child > .o-anchor-item-link .o-anchor-item-top-line { + display: none; } + &:last-child:not(.with-children) > .o-anchor-item-link .o-anchor-item-bottom-line { + display: none; + } +} + +.o-anchor-link-popover-wrapper { + max-width: var(--anchor-popover-max-width); } diff --git a/packages/opendesign/src/anchor/style/theme-ascend.index.ts b/packages/opendesign/src/anchor/style/theme-ascend.index.ts new file mode 100644 index 0000000000000000000000000000000000000000..b0049a088358468a6b9185cfea3d656b4f1faaf9 --- /dev/null +++ b/packages/opendesign/src/anchor/style/theme-ascend.index.ts @@ -0,0 +1,4 @@ +import '../../_styles'; +import '../../select/style/theme-ascend.index'; +import './index.scss'; +import './theme-ascend.scss'; diff --git a/packages/opendesign/src/anchor/style/theme-ascend.scss b/packages/opendesign/src/anchor/style/theme-ascend.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/packages/opendesign/src/anchor/style/theme-kunpeng.index.ts b/packages/opendesign/src/anchor/style/theme-kunpeng.index.ts new file mode 100644 index 0000000000000000000000000000000000000000..08f1ffe5704754a7322de7adff8f64b9f43f7379 --- /dev/null +++ b/packages/opendesign/src/anchor/style/theme-kunpeng.index.ts @@ -0,0 +1,4 @@ +import '../../_styles'; +import '../../select/style/theme-kunpeng.index'; +import './index.scss'; +import './theme-kunpeng.scss'; diff --git a/packages/opendesign/src/anchor/style/theme-kunpeng.scss b/packages/opendesign/src/anchor/style/theme-kunpeng.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/packages/opendesign/src/anchor/style/theme-openeuler.index.ts b/packages/opendesign/src/anchor/style/theme-openeuler.index.ts new file mode 100644 index 0000000000000000000000000000000000000000..93ba6509e35367f9c7ca12d2142dcb65d50c0b55 --- /dev/null +++ b/packages/opendesign/src/anchor/style/theme-openeuler.index.ts @@ -0,0 +1,4 @@ +import '../../_styles'; +import '../../select/style/theme-openeuler.index'; +import './index.scss'; +import './theme-openeuler.scss'; diff --git a/packages/opendesign/src/anchor/style/theme-openeuler.scss b/packages/opendesign/src/anchor/style/theme-openeuler.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/packages/opendesign/src/anchor/style/var.scss b/packages/opendesign/src/anchor/style/var.scss index 8d13c1f8acc1be9755c6982d73712f274890cfa3..1b66aaec7d501d7a2a425c8e016fca1c5f7c34e3 100644 --- a/packages/opendesign/src/anchor/style/var.scss +++ b/packages/opendesign/src/anchor/style/var.scss @@ -1,10 +1,16 @@ .o-anchor { - --anchor-line-width: 2px; + --anchor-line-width: 1px; --anchor-line-bg-color: var(--o-color-control4); + --anchor-line-gap: var(--o-gap-2); --anchor-indicator-width: 2px; - --anchor-indicator-height: 16px; + --anchor-indicator-height: 12px; --anchor-indicator-bg-color: var(--o-color-primary1); + --anchor-indicator-adjust: 0.12em; + + --anchor-circle-size: 8px; + --anchor-circle-ring-width: 1.5px; + --anchor-circle-gap: 8px; } .o-anchor-item { @@ -12,16 +18,54 @@ --anchor-item-link-color: var(--o-color-info2); --anchor-item-link-color-active: var(--o-color-primary1); + --anchor-item-link-color-hover: var(--o-color-primary2); + --anchor-item-link-color-press: var(--o-color-primary3); + --anchor-item-link-color-disabled: var(--o-color-info4); + + --anchor-item-max-row: 2; --anchor-item-link-text-size: var(--o-font_size-text1); --anchor-item-link-text-height: var(--o-line_height-text1); - --anchor-item-link-bg-color: transparent; - --anchor-item-link-bg-color-hover: var(--o-color-control2-light); - --anchor-item-link-bg-color-active: var(--o-color-control3-light); + --anchor-item-sub-link-text-size: var(--o-font_size-tip1); + --anchor-item-sub-link-text-height: var(--o-line_height-tip1); - --anchor-item-link-padding: 7px 8px; + --anchor-item-link-padding-h: 8px; + --anchor-item-link-padding-v: 8px; + --anchor-item-sub-link-padding-v: 5px; --anchor-item-link-radius: var(--o-radius_control-s); - --anchor-item-link-gap: 2px; + --anchor-item-text-indent: 12px; +} + +.o-anchor-small { + .o-anchor-item { + --anchor-item-link-text-size: var(--o-font_size-tip1); + --anchor-item-link-text-height: var(--o-line_height-tip1); + + --anchor-item-sub-link-text-size: var(--o-font_size-tip2); + --anchor-item-sub-link-text-height: var(--o-line_height-tip2); + + --anchor-item-link-padding-v: 5px; + --anchor-item-sub-link-padding-v: 5px; + } +} + +.o-anchor-menu { + .o-anchor-item { + --anchor-item-link-text-size: var(--o-font_size-tip1); + --anchor-item-link-text-height: var(--o-line_height-tip1); + + --anchor-item-sub-link-text-size: var(--o-font_size-tip1); + --anchor-item-sub-link-text-height: var(--o-line_height-tip1); + + --anchor-item-link-padding-v: 5px; + --anchor-item-sub-link-padding-v: 5px; + + --anchor-item-text-indent: 20px; + } +} + +.o-anchor-link-popover-wrapper { + --anchor-popover-max-width: 200px; } diff --git a/packages/opendesign/src/anchor/types.ts b/packages/opendesign/src/anchor/types.ts index 063c1ce0ccfe0c8093c3008990c5058caf625efc..862fb67f31251dcbae9e7d1cd60fb093d18e747f 100644 --- a/packages/opendesign/src/anchor/types.ts +++ b/packages/opendesign/src/anchor/types.ts @@ -1,7 +1,20 @@ import type { ExtractPropTypes, PropType } from 'vue'; +export const AnchorSizeTypes = ['medium', 'small', 'menu'] as const; +export type AnchorSizeT = (typeof AnchorSizeTypes)[number]; +export type AnchorTargetT = '_blank' | '_parent' | '_self' | '_top'; + export const anchorProps = { - /** + /** + * @zh-CN 锚点的尺寸, menu为左侧菜单或移动端菜单混合使用 + * @en-US Anchor size, 'menu' for aside menu-mixed or mobile menu mode + * @default 'medium' + */ + size: { + type: String as PropType, + default: 'medium', + }, + /** * @zh-CN 监测容器 * @en-US Scroll container to monitor * @default window @@ -9,7 +22,7 @@ export const anchorProps = { container: { type: [String, Object] as PropType, }, - /** + /** * @zh-CN 锚点激活的边界范围 * @en-US Boundary for anchor activation * @default 5 @@ -18,7 +31,7 @@ export const anchorProps = { type: Number, default: 5, }, - /** + /** * @zh-CN 锚点激活的判定边界 * @en-US Boundary for anchor activation * @default 0 @@ -31,7 +44,7 @@ export const anchorProps = { * @zh-CN 点击锚点时是否改变浏览器地址栏的 hash 值 * @en-US Whether to change the browser's address bar hash value when clicking the anchor * @default true - */ + */ changeHash: { type: Boolean, default: true, @@ -42,15 +55,15 @@ export const anchorItemProps = { /** * @zh-CN 锚点标题 * @en-US Anchor title - */ + */ title: { type: String, default: '', }, /** - * @zh-CN 锚点跳转的目标元素(带#前缀) + * @zh-CN 锚点监听、跳转的目标元素(带#前缀) * @en-US Target element for anchor navigation (with # prefix) - */ + */ href: { type: String, required: true, @@ -59,11 +72,20 @@ export const anchorItemProps = { * @zh-CN 锚点跳转方式 * @en-US Anchor navigation method * @default '_self' - */ + */ target: { - type: String as PropType<'_blank' | '_parent' | '_self' | '_top'>, + type: String as PropType, default: '_self', }, + /** + * @zh-CN 锚点是否禁用 + * @en-US Anchor disable status + * @default false + */ + disabled: { + type: Boolean as PropType, + default: false, + }, }; export type AnchorContainerT = HTMLElement | Window;