Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
391 changes: 391 additions & 0 deletions CCUI-OPTIMIZATION-RISK-REPORT.md

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion packages/ccui/ui/affix/src/affix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export default defineComponent({

let container: HTMLElement | Window | null = null
let resizeObserver: ResizeObserver | null = null
let rafId: number | null = null

const isTopMode = computed(() => props.offsetBottom === undefined)
const offsetTop = computed(() => props.offsetTop ?? 0)
Expand Down Expand Up @@ -140,14 +141,19 @@ export default defineComponent({
resizeObserver.observe(wrapperRef.value)
}
// 等下一帧再计算,避免初次布局未完成
requestAnimationFrame(() => update())
rafId = requestAnimationFrame(() => update())
})

onBeforeUnmount(() => {
unbindContainer()
window.removeEventListener('resize', update)
resizeObserver?.disconnect()
resizeObserver = null
// 卸载时取消未执行的下一帧回调,避免卸载后再触发一次 update()
if (rafId !== null && typeof cancelAnimationFrame === 'function') {
cancelAnimationFrame(rafId)
rafId = null
}
})

watch(
Expand Down
2 changes: 1 addition & 1 deletion packages/ccui/ui/alert/src/alert.scss
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@

.#{$cls-prefix}-alert__icon {
margin-top: 4px;
font-size: 24px;
font-size: $ccui-font-size-heading-3;
}

.#{$cls-prefix}-alert__message {
Expand Down
10 changes: 6 additions & 4 deletions packages/ccui/ui/alert/src/alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ export default defineComponent({
const ns = useNamespace('alert')
const closed = ref(false)

// 是否渲染图标:显式开启、提供 icon 插槽、或 banner 模式
const showIcon = computed(() => props.showIcon || !!slots.icon || props.banner)

const cls = computed(() => ({
[ns.b()]: true,
[ns.m(props.type)]: true,
[ns.m('with-description')]: !!props.description || !!slots.description,
[ns.m('show-icon')]: props.showIcon || !!slots.icon,
[ns.m('show-icon')]: showIcon.value,
[ns.m('banner')]: props.banner,
}))

Expand All @@ -39,10 +42,9 @@ export default defineComponent({
if (closed.value) {
return null
}
const showIcon = props.showIcon || !!slots.icon || props.banner
return (
<div class={[cls.value, props.classNames?.root]} style={props.styles?.root} role="alert">
{showIcon && (
{showIcon.value && (
<span class={[ns.e('icon'), props.classNames?.icon]} style={props.styles?.icon}>
{slots.icon ? (
slots.icon()
Expand All @@ -64,7 +66,7 @@ export default defineComponent({
)}
</div>
{(props.closable || props.closeText) && (
<button class={ns.e('close')} type="button" onClick={onClose}>
<button class={ns.e('close')} type="button" aria-label="关闭" onClick={onClose}>
{props.closeText ? (
<span>{props.closeText}</span>
) : (
Expand Down
4 changes: 0 additions & 4 deletions packages/ccui/ui/anchor/src/anchor-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ export const anchorProps = {
type: [String, Object] as PropType<string | HTMLElement>,
default: undefined,
},
showInkInFixed: {
type: Boolean,
default: false,
},
targetOffset: {
type: Number,
default: undefined,
Expand Down
49 changes: 37 additions & 12 deletions packages/ccui/ui/anchor/src/anchor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,23 @@ export default defineComponent({
const inkRef = ref<HTMLElement>()
const linkRefs = ref<Map<string, HTMLElement>>(new Map())

const setLinkRef = (href: string) => (el: unknown) => {
const node = el as HTMLElement | null
if (node) {
linkRefs.value.set(href, node)
} else {
linkRefs.value.delete(href)
// 缓存每个 href 的 ref 回调,保证同一 href 始终复用同一函数引用,
// 避免每次渲染产生新函数导致 Vue 反复 detach/attach(null→node)抖动。
const linkRefSetters = new Map<string, (el: unknown) => void>()
const getLinkRef = (href: string) => {
let setter = linkRefSetters.get(href)
if (!setter) {
setter = (el: unknown) => {
const node = el as HTMLElement | null
if (node) {
linkRefs.value.set(href, node)
} else {
linkRefs.value.delete(href)
}
}
linkRefSetters.set(href, setter)
}
return setter
}

const updateInk = () => {
Expand Down Expand Up @@ -130,14 +140,29 @@ export default defineComponent({
}

let container: HTMLElement | Window | null = null
onMounted(() => {
const bind = () => {
container = getScrollContainer(props.scrollContainer)
container.addEventListener('scroll', onScroll, { passive: true })
onScroll()
})
onBeforeUnmount(() => {
}
const unbind = () => {
container?.removeEventListener('scroll', onScroll)
container = null
}
onMounted(() => {
bind()
onScroll()
})
onBeforeUnmount(unbind)
// scrollContainer 运行时变化时,需把监听从旧容器迁移到新容器并重算高亮,
// 否则旧容器监听泄漏、新容器无监听导致滚动驱动失效。
watch(
() => props.scrollContainer,
() => {
unbind()
bind()
onScroll()
},
)

watch(activeLink, () => {
nextTick(updateInk)
Expand Down Expand Up @@ -177,15 +202,15 @@ export default defineComponent({
return (
<div class={ns.e('link')} key={link.href}>
<a
ref={setLinkRef(link.href)}
ref={getLinkRef(link.href)}
class={[ns.e('link-title'), active && ns.em('link-title', 'active')]}
href={link.href}
style={{ paddingInlineStart: `${16 + level * 16}px` }}
onClick={(e: MouseEvent) => onLinkClick(e, link)}
>
{link.title ?? link.href}
</a>
{link.children?.length && (
{!!link.children?.length && (
<div class={ns.e('children')}>{link.children.map((child) => renderLink(child, level + 1))}</div>
)}
</div>
Expand Down
22 changes: 10 additions & 12 deletions packages/ccui/ui/auto-complete/src/auto-complete.scss
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,15 @@
border: 0;
outline: 0;
background: transparent;
color: rgba(0, 0, 0, 0.88);
color: $ccui-color-text;
font-size: 14px;

&::placeholder {
color: rgba(0, 0, 0, 0.25);
color: $ccui-color-text-placeholder;
}

&:disabled {
color: rgba(0, 0, 0, 0.4);
color: $ccui-color-text-disabled;
}
}

Expand All @@ -81,24 +81,22 @@
justify-content: center;
width: 16px;
height: 16px;
color: rgba(0, 0, 0, 0.45);
color: $ccui-color-text-tertiary;
font-size: 11px;
cursor: pointer;
border-radius: 50%;
transition: color 0.2s;

&:hover {
color: rgba(0, 0, 0, 0.85);
color: $ccui-color-text;
}
}

&__panel {
background: $ccui-base-bg;
border: 1px solid $ccui-dividing-line;
border-radius: 6px;
box-shadow:
0 6px 16px rgba(0, 0, 0, 0.08),
0 3px 6px -4px rgba(0, 0, 0, 0.12);
box-shadow: $ccui-box-shadow-secondary;
box-sizing: border-box;
overflow: auto;
z-index: 1050;
Expand All @@ -113,25 +111,25 @@

&__option {
padding: 5px 12px;
color: rgba(0, 0, 0, 0.88);
color: $ccui-color-text;
font-size: 14px;
cursor: pointer;
transition: background 0.2s;

&.is-active {
background: rgba(0, 0, 0, 0.04);
background: $ccui-color-fill-tertiary;
}

&.is-disabled {
color: rgba(0, 0, 0, 0.25);
color: $ccui-color-text-disabled;
cursor: not-allowed;
background: transparent;
}
}

&__empty {
padding: 12px;
color: rgba(0, 0, 0, 0.45);
color: $ccui-color-text-tertiary;
font-size: 13px;
text-align: center;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/ccui/ui/avatar/src/avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default defineComponent({
fontSize.value = minNum.value / 4 + 3

// 传入的name不存在 且不等于 '' 时
isNobody.value = !!name.value && name.value === ''
isNobody.value = !name.value

// 计算背景颜色code
BgColorCode.value = useGetBackgroundColor(gender.value, nameDisplay.value.substring(0, 1))
Expand All @@ -54,7 +54,7 @@ export default defineComponent({
const imgElement = (
<img
src={imgSrc.value}
alt=""
alt={name.value || customText.value || ''}
onError={showErrorAvatar}
class={[props.classNames?.image]}
style={
Expand Down
29 changes: 0 additions & 29 deletions packages/ccui/ui/avatar/src/components/icon-nobody.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { ref } from 'vue'

export default function getBackgroundColor(gender: string, char: string): number {
const code = ref<number>(1)
export default function useGetBackgroundColor(gender: string, char: string): number {
let code = 1
// 性别存在 直接使用性别
if (gender) {
if (gender.toLowerCase() === 'male') {
code.value = 1
code = 1
} else if (gender.toLowerCase() === 'female') {
code.value = 0
code = 0
} else {
throw new Error('gender must be "Male" or "Female"')
}
} else {
const unicode = char.charCodeAt(0)
code.value = unicode % 2
code = unicode % 2
}
return code.value
return code
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default function useGetDisplayName(name: string, customText: string, widt
nameDisplay = name.length < 2 ? name : nameFormatting(name)
}

if (width < 30) {
if (name && width < 30) {
nameDisplay = name.substring(0, 1).toUpperCase()
}
return nameDisplay
Expand Down
10 changes: 9 additions & 1 deletion packages/ccui/ui/badge/src/badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ export default defineComponent({
}
if (props.offset && Array.isArray(props.offset)) {
const [x, y] = props.offset
style.transform = `translate(calc(-50% + ${x}px), calc(-50% + ${y}px))`
// 独立模式以 scss 基线 transform: none 为锚做纯平移;
// 包裹模式以 scss 基线 translate(50%, -50%)(角标推到右上角外侧)为锚做微调
style.transform = isStandalone.value
? `translate(${x}px, ${y}px)`
: `translate(calc(50% + ${x}px), calc(-50% + ${y}px))`
}
return Object.keys(style).length ? style : undefined
})
Expand Down Expand Up @@ -82,6 +86,8 @@ export default defineComponent({
<span
class={[ns.b(), ns.m('count-standalone'), props.classNames?.root, props.classNames?.count]}
style={[countStyle.value, props.styles?.root, props.styles?.count] as any}
// 溢出时 displayCount 会截断成 99+,用 title 暴露真实数值给读屏/悬停
title={props.dot ? undefined : typeof props.count === 'number' ? String(props.count) : displayCount.value}
>
{displayCount.value}
</span>
Expand All @@ -97,6 +103,8 @@ export default defineComponent({
props.dot ? props.classNames?.dot : props.classNames?.count,
]}
style={[countStyle.value, props.dot ? props.styles?.dot : props.styles?.count] as any}
// 溢出时 displayCount 会截断成 99+,用 title 暴露真实数值给读屏/悬停(dot 模式无数值)
title={props.dot ? undefined : typeof props.count === 'number' ? String(props.count) : displayCount.value}
>
{props.dot ? null : displayCount.value}
</sup>
Expand Down
2 changes: 1 addition & 1 deletion packages/ccui/ui/border-beam/src/border-beam.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@use '../../style-var/index.scss' as *;

// 对标 Ant Design border-beam 的实现:mask 取边框环 + offset-path 矩形路径(固定 round 100px,
// 实现:mask 取边框环 + offset-path 矩形路径(固定 round 100px,
// 大圆角让过弯旋转平滑无卡顿)+ offset-anchor 90% 50% 形成拖尾彗星,offset-distance 0→100% 循环。
.#{$cls-prefix}-border-beam {
position: relative;
Expand Down
3 changes: 2 additions & 1 deletion packages/ccui/ui/breadcrumb/src/breadcrumb.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

&:hover {
color: $ccui-color-text;
background-color: rgba(0, 0, 0, 0.06);
background-color: $ccui-color-fill-secondary;
border-radius: $ccui-border-radius-sm;
}
}
Expand All @@ -46,6 +46,7 @@
user-select: none;
}

// 末项分隔符依赖 BreadcrumbItem 为 nav 的最后一个直接子节点;若其后另有节点则 :last-child 失效
&__item:last-child &__separator {
display: none;
}
Expand Down
Loading
Loading