refactor(bitable): simplify code after ce-simplify-code pass

Behavior-preserving simplifications (net -22 lines):
- useResponsiveBreakpoint: remove createHandler factory, share single sync fn
- RecordDetailDrawer: remove isEditable wrapper, call isFieldEditable directly
- ViewConfigPanel: merge duplicate saveGrouping/saveConditionalFormat into saveU5Config
- groupingRulesUtils: use Array.find instead of for-loop, simplify Number.isFinite check
- GroupingEditor: simplify filter callback to single-expression arrow

Verified: typecheck + build:frontend + ruff all pass.

Refs: ce-simplify-code (LFG Step 3)
This commit is contained in:
chiguyong 2026-07-04 00:28:28 +08:00
parent 229dc0b2f3
commit 137bda0361
5 changed files with 24 additions and 46 deletions

View File

@ -127,14 +127,13 @@ watch(
// these have opaque values that don't form meaningful groups. Number/text/
// date/select/multiselect all group by their scalar value.
const selectableFields = computed(() =>
props.fields.filter((f) => {
return (
props.fields.filter(
(f) =>
f.field_type !== 'formula' &&
f.field_type !== 'lookup' &&
f.field_type !== 'attachment' &&
f.field_type !== 'image'
)
}),
f.field_type !== 'image',
),
)
function fieldName(fieldId: string): string {

View File

@ -67,7 +67,7 @@
</span>
</template>
<!-- Read-only renders -->
<template v-if="!isEditable(f)">
<template v-if="!isFieldEditable(f)">
<SelectDisplay
v-if="f.field_type === 'select' || f.field_type === 'multiselect'"
:value="(record.values[f.id] as string | string[] | null | undefined)"
@ -232,13 +232,9 @@ const showFooter = computed(
() =>
!!record.value &&
fields.value.length > 0 &&
fields.value.some(isEditable),
fields.value.some(isFieldEditable),
)
function isEditable(f: IBitableField): boolean {
return isFieldEditable(f)
}
function isAgentOwned(f: IBitableField): boolean {
return f.owner === 'agent'
}
@ -264,7 +260,7 @@ watch(
}
if (!rec) return
for (const f of fields.value) {
if (!isEditable(f)) continue
if (!isFieldEditable(f)) continue
// Clone primitive values; clone arrays so multiselect edits don't
// mutate the store's record.
const v = rec.values[f.id]
@ -284,7 +280,7 @@ watch(
// Re-trigger hydration by reassigning via the record watch.
for (const k of Object.keys(editBuffer)) delete editBuffer[k]
for (const f of fields.value) {
if (!isEditable(f)) continue
if (!isFieldEditable(f)) continue
const v = record.value.values[f.id]
editBuffer[f.id] = Array.isArray(v) ? [...v] : (v ?? undefined)
}
@ -308,7 +304,7 @@ async function onSubmit(): Promise<void> {
// from currentRecord.values inside updateRecordFields (preserves them).
const edited: Record<string, unknown> = {}
for (const f of fields.value) {
if (!isEditable(f)) continue
if (!isFieldEditable(f)) continue
if (f.id in editBuffer) edited[f.id] = editBuffer[f.id]
}
const ok = await store.updateRecordFields(props.recordId, edited)

View File

@ -69,7 +69,7 @@
:fields="fields"
/>
<div class="view-config-panel__actions">
<a-button type="primary" @click="saveGrouping">保存分组</a-button>
<a-button type="primary" @click="saveU5Config">保存分组</a-button>
</div>
</a-tab-pane>
@ -80,7 +80,7 @@
:fields="fields"
/>
<div class="view-config-panel__actions">
<a-button type="primary" @click="saveConditionalFormat">保存条件格式</a-button>
<a-button type="primary" @click="saveU5Config">保存条件格式</a-button>
</div>
</a-tab-pane>
</a-tabs>
@ -210,15 +210,9 @@ async function saveHidden(): Promise<void> {
// U5: save group_by + conditional_formatting through the new
// updateViewConfig action (which calls the same PATCH /views endpoint;
// the route layer validates the U5 sub-keys and 422s on invalid input).
async function saveGrouping(): Promise<void> {
if (!props.view) return
await store.updateViewConfig(props.view.id, {
group_by: groupByItems.value,
conditional_formatting: cfRules.value,
})
}
async function saveConditionalFormat(): Promise<void> {
// Both tabs' save buttons call this saving either tab persists both U5
// keys together (matches the existing merge-then-PATCH behavior).
async function saveU5Config(): Promise<void> {
if (!props.view) return
await store.updateViewConfig(props.view.id, {
group_by: groupByItems.value,

View File

@ -57,26 +57,18 @@ export function useResponsiveBreakpoint(): ResponsiveBreakpoint {
isDesktop.value = !isMobile.value && !isTablet.value
}
function createHandler(): () => void {
return () => sync()
}
const mobileHandler = createHandler()
const tabletHandler = createHandler()
const wideHandler = createHandler()
// sync 是稳定的函数引用3 个 mql 共用同一 handler每个 mql 独立 add/remove
onMounted(() => {
sync()
// addEventListener('change', ...) 是现代标准 API
mobileMql?.addEventListener('change', mobileHandler)
tabletMql?.addEventListener('change', tabletHandler)
wideMql?.addEventListener('change', wideHandler)
mobileMql?.addEventListener('change', sync)
tabletMql?.addEventListener('change', sync)
wideMql?.addEventListener('change', sync)
})
onUnmounted(() => {
mobileMql?.removeEventListener('change', mobileHandler)
tabletMql?.removeEventListener('change', tabletHandler)
wideMql?.removeEventListener('change', wideHandler)
mobileMql?.removeEventListener('change', sync)
tabletMql?.removeEventListener('change', sync)
wideMql?.removeEventListener('change', sync)
})
return { isMobile, isTablet, isDesktop, isWide }

View File

@ -259,11 +259,7 @@ export function findFirstMatchingRule(
value: unknown,
rules: ConditionalFormatRule[] | undefined,
): ConditionalFormatRule | null {
if (!rules || rules.length === 0) return null
for (const rule of rules) {
if (matchConditionalFormatRule(value, rule)) return rule
}
return null
return rules?.find((rule) => matchConditionalFormatRule(value, rule)) ?? null
}
// ---------------------------------------------------------------------------
@ -356,7 +352,8 @@ function sortGroupKeys(keys: string[], direction: GroupDirection): void {
for (const k of keys) {
if (k === '') continue
const n = Number(k)
if (k !== '' && !Number.isNaN(n) && Number.isFinite(n)) {
// Number.isFinite excludes NaN/Infinity; '' already skipped above.
if (Number.isFinite(n)) {
numericKeys.push({ value: n, key: k })
} else {
stringKeys.push(k)