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

View File

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

View File

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

View File

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

View File

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