feat(bitable): U1 add design token system + vxe-table dependency declaration

- Add bitable-tokens.css with 4 token categories (color/spacing/radius/font/drawer-width)
- Add FieldTypeIcon.vue mapping 9 field types to Ant Design Outlined icons
- Add useResponsiveBreakpoint composable (768/1024/1440 breakpoints)
- Add LoadingState (skeleton) and ErrorState (inline alert + retry) components
- Token化 9 bitable components/views (replace hardcoded hex with var())
- Declare vxe-table dependency explicitly (resolve ghost dependency)
- Upgrade SelectDisplay chip palette to 8-color token with WCAG AA contrast

Phase 1 foundation for Phase 2 UX work (U2-U5).

Refs: docs/plans/2026-07-03-001-feat-bitable-p0-ux-and-agent-parity-plan.md U1
This commit is contained in:
chiguyong 2026-07-03 14:40:57 +08:00
parent 96ccca3d87
commit e1cf073693
16 changed files with 483 additions and 75 deletions

View File

@ -24,7 +24,8 @@
"markdown-it": "^14.2.0",
"pinia": "^2.2.0",
"vue": "^3.5.0",
"vue-router": "^4.4.0"
"vue-router": "^4.4.0",
"vxe-table": "^4.19.19"
},
"devDependencies": {
"@playwright/test": "^1.59.0",
@ -1699,6 +1700,19 @@
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vxe-ui/core": {
"version": "4.4.15",
"resolved": "https://registry.npmmirror.com/@vxe-ui/core/-/core-4.4.15.tgz",
"integrity": "sha512-DPNPnjSnypg8XO44ApcHw/nJNNJUYF85k1IVFJU8nyKj3qvYBFP96m4ZLOhc6tlMmMJWmhbzXCWZOb37dUNctQ==",
"license": "MIT",
"dependencies": {
"dom-zindex": "^1.0.7",
"xe-utils": "^4.0.10"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/acorn": {
"version": "8.17.0",
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.17.0.tgz",
@ -2045,6 +2059,12 @@
"integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==",
"license": "MIT"
},
"node_modules/dom-zindex": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/dom-zindex/-/dom-zindex-1.0.7.tgz",
"integrity": "sha512-cKU/h8v8IPBgdZOTPbPmq3Ib+Ac5C+kKoh9I4LbGR9BM3GwbmB16KYWKJcj5M2BavnA66EbgYzxYDLd1IytnlQ==",
"license": "MIT"
},
"node_modules/dompurify": {
"version": "3.4.10",
"resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.4.10.tgz",
@ -3320,6 +3340,24 @@
"vue": "^3.0.0"
}
},
"node_modules/vxe-pc-ui": {
"version": "4.15.21",
"resolved": "https://registry.npmmirror.com/vxe-pc-ui/-/vxe-pc-ui-4.15.21.tgz",
"integrity": "sha512-8uCUelYE2OyhKOUMCZ9DffRQvIuTpEYm3LsiwQ/XiKNq4qOdFY8RlBEzGY8MnZnpm+kKDXvynFFS59inQT42Rg==",
"license": "MIT",
"dependencies": {
"@vxe-ui/core": "^4.4.15"
}
},
"node_modules/vxe-table": {
"version": "4.19.23",
"resolved": "https://registry.npmmirror.com/vxe-table/-/vxe-table-4.19.23.tgz",
"integrity": "sha512-kg6nzIkGCea4otjoxzWKVdcshUa00eswSlVZgo0cNIxpmmOMMIdmcY34IzJXl+UyYd/rvFGVjRidy/R97yaqVA==",
"license": "MIT",
"dependencies": {
"vxe-pc-ui": "^4.14.0"
}
},
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz",
@ -3373,6 +3411,12 @@
"node": ">=8"
}
},
"node_modules/xe-utils": {
"version": "4.0.10",
"resolved": "https://registry.npmmirror.com/xe-utils/-/xe-utils-4.0.10.tgz",
"integrity": "sha512-HLj9r+EjCh6e0J1vfZhDKPwaTtONMl0GNK0OYR6b3KU4QHHpOXNFzEhgwe35KFy8dqsK2sm2WFRzHRafqkYgMA==",
"license": "MIT"
},
"node_modules/zrender": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-6.1.0.tgz",

View File

@ -33,7 +33,8 @@
"markdown-it": "^14.2.0",
"pinia": "^2.2.0",
"vue": "^3.5.0",
"vue-router": "^4.4.0"
"vue-router": "^4.4.0",
"vxe-table": "^4.19.19"
},
"devDependencies": {
"@playwright/test": "^1.59.0",

View File

@ -45,10 +45,10 @@ function formatSize(bytes: number): string {
.attachment-cell__link {
display: inline-flex;
align-items: center;
gap: 4px;
color: var(--color-primary, #1a1a1a);
gap: var(--bitable-spacing-xs);
color: var(--bitable-color-primary);
text-decoration: none;
font-size: 12px;
font-size: var(--bitable-font-xs);
}
.attachment-cell__link:hover {
@ -63,11 +63,11 @@ function formatSize(bytes: number): string {
}
.attachment-cell__size {
color: var(--text-secondary, #8c8c8c);
color: var(--bitable-color-text-secondary);
font-size: 11px;
}
.attachment-cell__empty {
color: var(--text-placeholder, #bfbfbf);
color: var(--bitable-color-text-placeholder);
}
</style>

View File

@ -292,35 +292,35 @@ defineExpose({
/* KTD10: CSS isolation all vxe-table style overrides scoped to
.bitable-grid-scope. Use :deep() to reach vxe-table internals. */
.bitable-grid-scope :deep(.vxe-table) {
font-size: 13px;
font-size: var(--bitable-font-sm);
}
.bitable-grid-scope :deep(.vxe-header--column) {
background: var(--bg-secondary, #fafafa);
background: var(--bitable-color-bg-secondary);
font-weight: 600;
}
.bitable-grid-scope :deep(.vxe-body--column.is--dirty) {
background: var(--bg-tertiary, #f3f4f6);
background: var(--bitable-color-bg-tertiary);
}
.bitable-grid-scope :deep(.vxe-cell--dirty) {
color: var(--color-primary, #1a1a1a);
color: var(--bitable-color-primary);
}
.bitable-grid-scope__add-col {
display: flex;
align-items: center;
gap: 4px;
gap: var(--bitable-spacing-xs);
cursor: pointer;
color: var(--text-secondary, #8c8c8c);
font-size: 12px;
padding: 0 8px;
color: var(--bitable-color-text-secondary);
font-size: var(--bitable-font-xs);
padding: 0 var(--bitable-spacing-sm);
height: 100%;
user-select: none;
}
.bitable-grid-scope__add-col:hover {
color: var(--color-primary, #1a1a1a);
color: var(--bitable-color-primary);
}
</style>

View File

@ -59,7 +59,7 @@ function handleMenuClick({ key }: { key: string }): void {
.column-header-menu {
display: flex;
align-items: center;
gap: 4px;
gap: var(--bitable-spacing-xs);
cursor: pointer;
width: 100%;
height: 100%;
@ -72,12 +72,12 @@ function handleMenuClick({ key }: { key: string }): void {
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 600;
font-size: 13px;
font-size: var(--bitable-font-sm);
}
.column-header-menu__arrow {
font-size: 10px;
color: var(--text-secondary, #8c8c8c);
color: var(--bitable-color-text-secondary);
flex-shrink: 0;
opacity: 0.6;
transition: opacity 0.15s;

View File

@ -0,0 +1,48 @@
<template>
<a-alert
class="bitable-error-state"
type="error"
show-icon
:message="message"
:description="description"
role="alert"
>
<template v-if="$slots.action || retryable" #action>
<slot name="action">
<a-button size="small" :loading="retrying" @click="emit('retry')">
重试
</a-button>
</slot>
</template>
</a-alert>
</template>
<script setup lang="ts">
import { Alert as AAlert, Button as AButton } from 'ant-design-vue'
withDefaults(
defineProps<{
message?: string
description?: string
retryable?: boolean
retrying?: boolean
}>(),
{
message: '加载失败',
description: '请检查网络后重试',
retryable: true,
retrying: false,
},
)
const emit = defineEmits<{
(e: 'retry'): void
}>()
</script>
<style scoped>
.bitable-error-state {
margin: var(--bitable-spacing-md, 12px) 0;
border-radius: var(--bitable-radius-md, 6px);
}
</style>

View File

@ -0,0 +1,49 @@
<template>
<component :is="iconComponent" class="field-type-icon" />
</template>
<script setup lang="ts">
import { computed, type Component } from 'vue'
import {
FileTextOutlined,
NumberOutlined,
CalendarOutlined,
TagOutlined,
TagsOutlined,
PaperClipOutlined,
PictureOutlined,
FunctionOutlined,
LinkOutlined,
QuestionOutlined,
} from '@ant-design/icons-vue'
import type { FieldType } from '@/api/bitable'
const props = defineProps<{
type: FieldType
}>()
// 9 Ant Design Outlined KTD1/plan U1 step 3
const ICON_MAP: Record<FieldType, Component> = {
text: FileTextOutlined,
number: NumberOutlined,
date: CalendarOutlined,
select: TagOutlined,
multiselect: TagsOutlined,
attachment: PaperClipOutlined,
image: PictureOutlined,
formula: FunctionOutlined,
lookup: LinkOutlined,
}
const iconComponent = computed<Component>(
() => ICON_MAP[props.type] ?? QuestionOutlined,
)
</script>
<style scoped>
.field-type-icon {
font-size: var(--bitable-font-sm);
color: var(--bitable-color-text-tertiary);
flex-shrink: 0;
}
</style>

View File

@ -52,18 +52,18 @@ function formatDate(iso: string): string {
<style scoped>
.file-card {
display: flex;
gap: 12px;
padding: 16px;
background: var(--bg-primary, #fff);
border: 1px solid var(--border-color, #f0f0f0);
border-radius: 8px;
gap: var(--bitable-spacing-md);
padding: var(--bitable-spacing-lg);
background: var(--bitable-color-bg);
border: 1px solid var(--bitable-color-border);
border-radius: var(--bitable-radius-lg);
cursor: pointer;
transition: all 0.2s;
height: 100%;
}
.file-card:hover {
border-color: var(--color-primary, #1a1a1a);
border-color: var(--bitable-color-primary);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transform: translateY(-2px);
}
@ -77,7 +77,7 @@ function formatDate(iso: string): string {
display: flex;
align-items: center;
justify-content: center;
color: var(--color-primary, #1a1a1a);
color: var(--bitable-color-primary);
}
.file-card__body {
@ -85,21 +85,21 @@ function formatDate(iso: string): string {
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
gap: var(--bitable-spacing-xs);
}
.file-card__name {
font-size: 14px;
font-size: var(--bitable-font-md);
font-weight: 600;
color: var(--text-primary, #1f1f1f);
color: var(--bitable-color-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-card__desc {
font-size: 12px;
color: var(--text-secondary, #8c8c8c);
font-size: var(--bitable-font-xs);
color: var(--bitable-color-text-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@ -108,6 +108,6 @@ function formatDate(iso: string): string {
.file-card__meta {
font-size: 11px;
color: var(--text-placeholder, #bfbfbf);
color: var(--bitable-color-text-placeholder);
}
</style>

View File

@ -83,21 +83,21 @@ onUnmounted(() => {
.image-cell {
display: flex;
flex-wrap: wrap;
gap: 4px;
gap: var(--bitable-spacing-xs);
padding: 2px 0;
}
.image-cell__thumb {
width: 40px;
height: 40px;
border-radius: 4px;
border-radius: var(--bitable-radius-sm);
overflow: hidden;
cursor: pointer;
background: var(--bg-secondary, #f5f5f5);
background: var(--bitable-color-bg-secondary);
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--border-color, #f0f0f0);
border: 1px solid var(--bitable-color-border);
}
.image-cell__img {
@ -107,11 +107,11 @@ onUnmounted(() => {
}
.image-cell__placeholder {
color: var(--text-placeholder, #bfbfbf);
font-size: 16px;
color: var(--bitable-color-text-placeholder);
font-size: var(--bitable-font-lg);
}
.image-cell__empty {
color: var(--text-placeholder, #bfbfbf);
color: var(--bitable-color-text-placeholder);
}
</style>

View File

@ -0,0 +1,42 @@
<template>
<div class="bitable-loading-state" role="status" aria-live="polite">
<a-skeleton :rows="rows" :title="title" active />
<span class="bitable-loading-state__sr-only">加载中</span>
</div>
</template>
<script setup lang="ts">
import { Skeleton as ASkeleton } from 'ant-design-vue'
withDefaults(
defineProps<{
// 5 grid/
rows?: number
// sticky header
title?: boolean
}>(),
{
rows: 5,
title: true,
},
)
</script>
<style scoped>
.bitable-loading-state {
padding: var(--bitable-spacing-lg, 16px);
}
/* 屏幕阅读器专用文本(视觉隐藏) */
.bitable-loading-state__sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>

View File

@ -1,28 +1,27 @@
<template>
<span v-if="multiple" class="select-display">
<a-tag
<span
v-for="v in (value as string[] | null | undefined) ?? []"
:key="v"
:color="colorOf(v)"
size="small"
class="select-display__chip"
:style="chipStyle(colorKeyFor(v))"
>
{{ labelOf(v) }}
</a-tag>
</span>
</span>
<a-tag
<span
v-else-if="value != null && value !== ''"
:color="colorOf(value as string)"
size="small"
class="select-display__chip"
:style="chipStyle(colorKeyFor(value as string))"
>
{{ labelOf(value as string) }}
</a-tag>
</span>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { Tag as ATag } from 'ant-design-vue'
interface ISelectOption {
export interface ISelectOption {
label: string
value: string
color?: string
@ -34,6 +33,21 @@ const props = defineProps<{
multiple?: boolean
}>()
// 8 token key bitable-tokens.css --bitable-cf-*
const COLOR_KEYS = [
'red',
'orange',
'yellow',
'green',
'blue',
'purple',
'gray',
'neutral',
] as const
type ColorKey = (typeof COLOR_KEYS)[number]
const COLOR_KEY_SET = new Set<string>(COLOR_KEYS)
// Normalize options to a lookup map
const optionMap = computed<Map<string, ISelectOption>>(() => {
const m = new Map<string, ISelectOption>()
@ -53,7 +67,52 @@ function labelOf(value: string): string {
return optionMap.value.get(value)?.label ?? value
}
function colorOf(value: string): string {
return optionMap.value.get(value)?.color ?? 'default'
// ponytail: hash 8 ceiling: hash
// chip
// : hash FNV-1a
function hashColorKey(value: string): ColorKey {
let h = 0
for (let i = 0; i < value.length; i++) {
h = (h * 31 + value.charCodeAt(i)) | 0
}
return COLOR_KEYS[Math.abs(h) % COLOR_KEYS.length]
}
// 8 key Ant Design preset
// 'default'/'pink'退 hash
function colorKeyFor(value: string): ColorKey {
const explicit = optionMap.value.get(value)?.color
if (explicit && COLOR_KEY_SET.has(explicit)) {
return explicit as ColorKey
}
return hashColorKey(value)
}
// chip -bg + -fg / ~12:1WCAG AA 4.5:1
function chipStyle(key: ColorKey): Record<string, string> {
return {
backgroundColor: `var(--bitable-cf-${key}-bg)`,
color: `var(--bitable-cf-${key}-fg)`,
borderColor: `var(--bitable-cf-${key}-fg)`,
}
}
</script>
<style scoped>
.select-display {
display: inline-flex;
flex-wrap: wrap;
gap: var(--bitable-spacing-xs);
}
.select-display__chip {
display: inline-flex;
align-items: center;
padding: 0 var(--bitable-spacing-sm);
border-radius: var(--bitable-radius-sm);
border: 1px solid;
font-size: var(--bitable-font-xs);
line-height: 1.5;
white-space: nowrap;
}
</style>

View File

@ -0,0 +1,87 @@
/**
* useResponsiveBreakpoint composable
*
* plan U1 Open Questions
* - isMobile: viewport < 768px
* - isTablet: 768px viewport < 1024px
* - isDesktop: viewport 1024px
* - isWide: viewport 1440px使
*
* U3 RecordDetailDrawerisMobile
* U5 ViewConfigPanelisMobile U1 API
*
* window.matchMedia + change onUnmounted
* SSR window false ponytail: 不引入
* SSR matchMedia
*/
import { ref, onMounted, onUnmounted, type Ref } from 'vue'
export interface ResponsiveBreakpoint {
isMobile: Ref<boolean>
isTablet: Ref<boolean>
isDesktop: Ref<boolean>
isWide: Ref<boolean>
}
const MOBILE_MAX = 767 // < 768
const TABLET_MAX = 1023 // < 1024
const WIDE_MIN = 1440 // ≥ 1440
export function useResponsiveBreakpoint(): ResponsiveBreakpoint {
const isMobile = ref(false)
const isTablet = ref(false)
const isDesktop = ref(false)
const isWide = ref(false)
// matchMedia 不支持 change 事件的旧浏览器回退ponytail: 不做
// resize 节流兜底——现代浏览器均支持 matchMedia change避免额外
// 依赖 lodash/throttle已知 ceiling: IE11 不可用,本项目目标现代浏览器)
const mobileMql: MediaQueryList | null =
typeof window !== 'undefined' && window.matchMedia
? window.matchMedia(`(max-width: ${MOBILE_MAX}px)`)
: null
const tabletMql: MediaQueryList | null =
typeof window !== 'undefined' && window.matchMedia
? window.matchMedia(`(min-width: ${MOBILE_MAX + 1}px) and (max-width: ${TABLET_MAX}px)`)
: null
const wideMql: MediaQueryList | null =
typeof window !== 'undefined' && window.matchMedia
? window.matchMedia(`(min-width: ${WIDE_MIN}px)`)
: null
function sync(): void {
isMobile.value = mobileMql?.matches ?? false
isTablet.value = tabletMql?.matches ?? false
isWide.value = wideMql?.matches ?? false
// desktop = 既非 mobile 也非 tablet≥ 1024
isDesktop.value = !isMobile.value && !isTablet.value
}
function createHandler(): () => void {
return () => sync()
}
const mobileHandler = createHandler()
const tabletHandler = createHandler()
const wideHandler = createHandler()
onMounted(() => {
sync()
// addEventListener('change', ...) 是现代标准 API
mobileMql?.addEventListener('change', mobileHandler)
tabletMql?.addEventListener('change', tabletHandler)
wideMql?.addEventListener('change', wideHandler)
})
onUnmounted(() => {
mobileMql?.removeEventListener('change', mobileHandler)
tabletMql?.removeEventListener('change', tabletHandler)
wideMql?.removeEventListener('change', wideHandler)
})
return { isMobile, isTablet, isDesktop, isWide }
}
// ponytail self-check: 断点边界互斥性。运行时调用 sync() 后,
// isMobile/isTablet/isDesktop 三者有且仅有一个为 true基于互斥的
// matchMedia 查询。trivial 互斥逻辑,不另立测试文件。

View File

@ -2,6 +2,7 @@ import { createApp } from 'vue'
import { createPinia } from 'pinia'
import 'ant-design-vue/dist/reset.css'
import './styles'
import './styles/bitable-tokens.css'
import App from './App.vue'
import router from './router'
import { useAuthStore } from './stores/auth'

View File

@ -0,0 +1,77 @@
/**
* Bitable Design Tokens CSS Custom Properties
*
* `--bitable-*` 前缀避免与 Ant Design Vue 4 全局 token 冲突
* 颜色/间距/圆角/字号 4 token + 抽屉宽度Phase 2 (U2-U5) 所有
* bitable 组件必须引用本文件的 var()禁止硬编码 hex
*
* 颜色 token 映射到全局 tokens.css已支持暗色主题bitable 组件
* 不直接维护暗色覆写跟随全局主题切换
*
* 条件格式 8 --bitable-cf-*每色提供 -bg浅底+ -fg深字配对
* -bg 用于单元格/chip 背景配深色文本对比度 ~12:1
* -fg 用于文本/边框/图标在白底上对比度 4.5:1WCAG AA
* 颜色语义 key: red | orange | yellow | green | blue | purple | gray | neutral
*/
:root {
/* ── 颜色:基础语义(映射全局 token跟随暗色主题 ── */
--bitable-color-primary: var(--color-primary, #1a1a1a);
--bitable-color-bg: var(--bg-primary, #ffffff);
--bitable-color-bg-secondary: var(--bg-secondary, #fbfbfa);
--bitable-color-bg-tertiary: var(--bg-tertiary, #f7f7f5);
--bitable-color-text: var(--text-primary, #1a1a1a);
--bitable-color-text-secondary: var(--text-secondary, #4a4a4a);
--bitable-color-text-tertiary: var(--text-tertiary, #6b6b6a);
--bitable-color-text-placeholder: var(--text-placeholder, #9b9b9a);
--bitable-color-border: var(--border-color, #ededec);
--bitable-color-border-hover: var(--border-color-hover, #dfdfde);
--bitable-color-border-split: var(--border-color-split, #f2f2f0);
/* ── 颜色:条件格式 8 色(语义 key → bg/fg 配对) ── */
/* red */
--bitable-cf-red-bg: #fee2e2;
--bitable-cf-red-fg: #b91c1c;
/* orange */
--bitable-cf-orange-bg: #ffedd5;
--bitable-cf-orange-fg: #9a3412;
/* yellow深金以保证 AA */
--bitable-cf-yellow-bg: #fef9c3;
--bitable-cf-yellow-fg: #a16207;
/* green */
--bitable-cf-green-bg: #dcfce7;
--bitable-cf-green-fg: #15803d;
/* blue */
--bitable-cf-blue-bg: #dbeafe;
--bitable-cf-blue-fg: #1d4ed8;
/* purple */
--bitable-cf-purple-bg: #f3e8ff;
--bitable-cf-purple-fg: #7e22ce;
/* gray */
--bitable-cf-gray-bg: #f3f4f6;
--bitable-cf-gray-fg: #374151;
/* neutral与 gray 区分:更纯的中性灰) */
--bitable-cf-neutral-bg: #f5f5f4;
--bitable-cf-neutral-fg: #404040;
/* ── 间距 ── */
--bitable-spacing-xs: 4px;
--bitable-spacing-sm: 8px;
--bitable-spacing-md: 12px;
--bitable-spacing-lg: 16px;
--bitable-spacing-xl: 24px;
/* ── 圆角 ── */
--bitable-radius-sm: 4px;
--bitable-radius-md: 6px;
--bitable-radius-lg: 8px;
/* ── 字号 ── */
--bitable-font-xs: 12px;
--bitable-font-sm: 13px;
--bitable-font-md: 14px;
--bitable-font-lg: 16px;
/* ── 抽屉宽度KTD4: ≤10 字段 480px>10 字段 640px ── */
--bitable-drawer-width: 480px;
--bitable-drawer-width-wide: 640px;
}

View File

@ -45,7 +45,7 @@
<main class="bitable-file-detail-view__main">
<div v-if="!store.currentTable" class="bitable-file-detail-view__placeholder">
<TableOutlined style="font-size: 48px; color: var(--text-placeholder)" />
<TableOutlined style="font-size: 48px; color: var(--bitable-color-text-placeholder)" />
<p>请选择左侧的数据表或点击 + 新建数据表</p>
</div>
@ -280,40 +280,40 @@ async function handleDeleteField(field: IBitableField): Promise<void> {
height: 100vh;
width: 100vw;
overflow: hidden;
background: var(--bg-primary, #fff);
background: var(--bitable-color-bg);
}
.bitable-file-detail-view__topbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
border-bottom: 1px solid var(--border-color, #f0f0f0);
padding: var(--bitable-spacing-sm) var(--bitable-spacing-lg);
border-bottom: 1px solid var(--bitable-color-border);
flex-shrink: 0;
}
.bitable-file-detail-view__topbar-left {
display: flex;
align-items: center;
gap: 8px;
gap: var(--bitable-spacing-sm);
}
.bitable-file-detail-view__topbar-right {
display: flex;
align-items: center;
gap: 8px;
gap: var(--bitable-spacing-sm);
}
.bitable-file-detail-view__icon {
font-size: 20px;
color: var(--color-primary, #1a1a1a);
color: var(--bitable-color-primary);
display: inline-flex;
align-items: center;
}
.bitable-file-detail-view__title {
font-weight: 600;
font-size: 16px;
font-size: var(--bitable-font-lg);
}
.bitable-file-detail-view__body {
@ -342,28 +342,28 @@ async function handleDeleteField(field: IBitableField): Promise<void> {
align-items: center;
justify-content: center;
height: 100%;
gap: 16px;
color: var(--text-placeholder, #bfbfbf);
gap: var(--bitable-spacing-lg);
color: var(--bitable-color-text-placeholder);
}
.bitable-file-detail-view__grid-header {
display: flex;
align-items: baseline;
gap: 12px;
padding: 12px 16px;
border-bottom: 1px solid var(--border-color, #f0f0f0);
gap: var(--bitable-spacing-md);
padding: var(--bitable-spacing-md) var(--bitable-spacing-lg);
border-bottom: 1px solid var(--bitable-color-border);
flex-shrink: 0;
}
.bitable-file-detail-view__table-name {
margin: 0;
font-size: 16px;
font-size: var(--bitable-font-lg);
font-weight: 600;
}
.bitable-file-detail-view__field-count {
font-size: 12px;
color: var(--text-secondary, #8c8c8c);
font-size: var(--bitable-font-xs);
color: var(--bitable-color-text-secondary);
}
.bitable-file-detail-view__grid-container {
@ -375,8 +375,8 @@ async function handleDeleteField(field: IBitableField): Promise<void> {
.bitable-file-detail-view__load-more {
display: flex;
justify-content: center;
padding: 8px;
border-top: 1px solid var(--border-color, #f0f0f0);
padding: var(--bitable-spacing-sm);
border-top: 1px solid var(--bitable-color-border);
flex-shrink: 0;
}
</style>

View File

@ -189,16 +189,16 @@ function handleDelete(file: IBitableFile): void {
height: 100vh;
width: 100vw;
overflow: hidden;
background: var(--bg-secondary, #fafafa);
background: var(--bitable-color-bg-secondary);
}
.bitable-file-list-view__topbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
background: var(--bg-primary, #fff);
border-bottom: 1px solid var(--border-color, #f0f0f0);
padding: var(--bitable-spacing-sm) var(--bitable-spacing-lg);
background: var(--bitable-color-bg);
border-bottom: 1px solid var(--bitable-color-border);
flex-shrink: 0;
}
@ -248,7 +248,7 @@ function handleDelete(file: IBitableFile): void {
}
.bitable-file-list-view__option-icon {
font-size: 16px;
color: var(--color-primary, #1a1a1a);
font-size: var(--bitable-font-lg);
color: var(--bitable-color-primary);
}
</style>