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:
parent
229dc0b2f3
commit
137bda0361
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue