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:
parent
96ccca3d87
commit
e1cf073693
|
|
@ -24,7 +24,8 @@
|
||||||
"markdown-it": "^14.2.0",
|
"markdown-it": "^14.2.0",
|
||||||
"pinia": "^2.2.0",
|
"pinia": "^2.2.0",
|
||||||
"vue": "^3.5.0",
|
"vue": "^3.5.0",
|
||||||
"vue-router": "^4.4.0"
|
"vue-router": "^4.4.0",
|
||||||
|
"vxe-table": "^4.19.19"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.59.0",
|
"@playwright/test": "^1.59.0",
|
||||||
|
|
@ -1699,6 +1700,19 @@
|
||||||
"url": "https://github.com/sponsors/antfu"
|
"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": {
|
"node_modules/acorn": {
|
||||||
"version": "8.17.0",
|
"version": "8.17.0",
|
||||||
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.17.0.tgz",
|
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.17.0.tgz",
|
||||||
|
|
@ -2045,6 +2059,12 @@
|
||||||
"integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==",
|
"integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/dompurify": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.10",
|
||||||
"resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.4.10.tgz",
|
"resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.4.10.tgz",
|
||||||
|
|
@ -3320,6 +3340,24 @@
|
||||||
"vue": "^3.0.0"
|
"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": {
|
"node_modules/warning": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz",
|
"resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz",
|
||||||
|
|
@ -3373,6 +3411,12 @@
|
||||||
"node": ">=8"
|
"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": {
|
"node_modules/zrender": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-6.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-6.1.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,8 @@
|
||||||
"markdown-it": "^14.2.0",
|
"markdown-it": "^14.2.0",
|
||||||
"pinia": "^2.2.0",
|
"pinia": "^2.2.0",
|
||||||
"vue": "^3.5.0",
|
"vue": "^3.5.0",
|
||||||
"vue-router": "^4.4.0"
|
"vue-router": "^4.4.0",
|
||||||
|
"vxe-table": "^4.19.19"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.59.0",
|
"@playwright/test": "^1.59.0",
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,10 @@ function formatSize(bytes: number): string {
|
||||||
.attachment-cell__link {
|
.attachment-cell__link {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: var(--bitable-spacing-xs);
|
||||||
color: var(--color-primary, #1a1a1a);
|
color: var(--bitable-color-primary);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 12px;
|
font-size: var(--bitable-font-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachment-cell__link:hover {
|
.attachment-cell__link:hover {
|
||||||
|
|
@ -63,11 +63,11 @@ function formatSize(bytes: number): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachment-cell__size {
|
.attachment-cell__size {
|
||||||
color: var(--text-secondary, #8c8c8c);
|
color: var(--bitable-color-text-secondary);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachment-cell__empty {
|
.attachment-cell__empty {
|
||||||
color: var(--text-placeholder, #bfbfbf);
|
color: var(--bitable-color-text-placeholder);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -292,35 +292,35 @@ defineExpose({
|
||||||
/* KTD10: CSS isolation — all vxe-table style overrides scoped to
|
/* KTD10: CSS isolation — all vxe-table style overrides scoped to
|
||||||
.bitable-grid-scope. Use :deep() to reach vxe-table internals. */
|
.bitable-grid-scope. Use :deep() to reach vxe-table internals. */
|
||||||
.bitable-grid-scope :deep(.vxe-table) {
|
.bitable-grid-scope :deep(.vxe-table) {
|
||||||
font-size: 13px;
|
font-size: var(--bitable-font-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-grid-scope :deep(.vxe-header--column) {
|
.bitable-grid-scope :deep(.vxe-header--column) {
|
||||||
background: var(--bg-secondary, #fafafa);
|
background: var(--bitable-color-bg-secondary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-grid-scope :deep(.vxe-body--column.is--dirty) {
|
.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) {
|
.bitable-grid-scope :deep(.vxe-cell--dirty) {
|
||||||
color: var(--color-primary, #1a1a1a);
|
color: var(--bitable-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-grid-scope__add-col {
|
.bitable-grid-scope__add-col {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: var(--bitable-spacing-xs);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--text-secondary, #8c8c8c);
|
color: var(--bitable-color-text-secondary);
|
||||||
font-size: 12px;
|
font-size: var(--bitable-font-xs);
|
||||||
padding: 0 8px;
|
padding: 0 var(--bitable-spacing-sm);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-grid-scope__add-col:hover {
|
.bitable-grid-scope__add-col:hover {
|
||||||
color: var(--color-primary, #1a1a1a);
|
color: var(--bitable-color-primary);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ function handleMenuClick({ key }: { key: string }): void {
|
||||||
.column-header-menu {
|
.column-header-menu {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: var(--bitable-spacing-xs);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
@ -72,12 +72,12 @@ function handleMenuClick({ key }: { key: string }): void {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 13px;
|
font-size: var(--bitable-font-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-header-menu__arrow {
|
.column-header-menu__arrow {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: var(--text-secondary, #8c8c8c);
|
color: var(--bitable-color-text-secondary);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
transition: opacity 0.15s;
|
transition: opacity 0.15s;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -52,18 +52,18 @@ function formatDate(iso: string): string {
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.file-card {
|
.file-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: var(--bitable-spacing-md);
|
||||||
padding: 16px;
|
padding: var(--bitable-spacing-lg);
|
||||||
background: var(--bg-primary, #fff);
|
background: var(--bitable-color-bg);
|
||||||
border: 1px solid var(--border-color, #f0f0f0);
|
border: 1px solid var(--bitable-color-border);
|
||||||
border-radius: 8px;
|
border-radius: var(--bitable-radius-lg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-card:hover {
|
.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);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +77,7 @@ function formatDate(iso: string): string {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: var(--color-primary, #1a1a1a);
|
color: var(--bitable-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-card__body {
|
.file-card__body {
|
||||||
|
|
@ -85,21 +85,21 @@ function formatDate(iso: string): string {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: var(--bitable-spacing-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-card__name {
|
.file-card__name {
|
||||||
font-size: 14px;
|
font-size: var(--bitable-font-md);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-primary, #1f1f1f);
|
color: var(--bitable-color-text);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-card__desc {
|
.file-card__desc {
|
||||||
font-size: 12px;
|
font-size: var(--bitable-font-xs);
|
||||||
color: var(--text-secondary, #8c8c8c);
|
color: var(--bitable-color-text-secondary);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
@ -108,6 +108,6 @@ function formatDate(iso: string): string {
|
||||||
|
|
||||||
.file-card__meta {
|
.file-card__meta {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--text-placeholder, #bfbfbf);
|
color: var(--bitable-color-text-placeholder);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -83,21 +83,21 @@ onUnmounted(() => {
|
||||||
.image-cell {
|
.image-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 4px;
|
gap: var(--bitable-spacing-xs);
|
||||||
padding: 2px 0;
|
padding: 2px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-cell__thumb {
|
.image-cell__thumb {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 4px;
|
border-radius: var(--bitable-radius-sm);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: var(--bg-secondary, #f5f5f5);
|
background: var(--bitable-color-bg-secondary);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: 1px solid var(--border-color, #f0f0f0);
|
border: 1px solid var(--bitable-color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-cell__img {
|
.image-cell__img {
|
||||||
|
|
@ -107,11 +107,11 @@ onUnmounted(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-cell__placeholder {
|
.image-cell__placeholder {
|
||||||
color: var(--text-placeholder, #bfbfbf);
|
color: var(--bitable-color-text-placeholder);
|
||||||
font-size: 16px;
|
font-size: var(--bitable-font-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-cell__empty {
|
.image-cell__empty {
|
||||||
color: var(--text-placeholder, #bfbfbf);
|
color: var(--bitable-color-text-placeholder);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -1,28 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<span v-if="multiple" class="select-display">
|
<span v-if="multiple" class="select-display">
|
||||||
<a-tag
|
<span
|
||||||
v-for="v in (value as string[] | null | undefined) ?? []"
|
v-for="v in (value as string[] | null | undefined) ?? []"
|
||||||
:key="v"
|
:key="v"
|
||||||
:color="colorOf(v)"
|
class="select-display__chip"
|
||||||
size="small"
|
:style="chipStyle(colorKeyFor(v))"
|
||||||
>
|
>
|
||||||
{{ labelOf(v) }}
|
{{ labelOf(v) }}
|
||||||
</a-tag>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<a-tag
|
<span
|
||||||
v-else-if="value != null && value !== ''"
|
v-else-if="value != null && value !== ''"
|
||||||
:color="colorOf(value as string)"
|
class="select-display__chip"
|
||||||
size="small"
|
:style="chipStyle(colorKeyFor(value as string))"
|
||||||
>
|
>
|
||||||
{{ labelOf(value as string) }}
|
{{ labelOf(value as string) }}
|
||||||
</a-tag>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { Tag as ATag } from 'ant-design-vue'
|
|
||||||
|
|
||||||
interface ISelectOption {
|
export interface ISelectOption {
|
||||||
label: string
|
label: string
|
||||||
value: string
|
value: string
|
||||||
color?: string
|
color?: string
|
||||||
|
|
@ -34,6 +33,21 @@ const props = defineProps<{
|
||||||
multiple?: boolean
|
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
|
// Normalize options to a lookup map
|
||||||
const optionMap = computed<Map<string, ISelectOption>>(() => {
|
const optionMap = computed<Map<string, ISelectOption>>(() => {
|
||||||
const m = new 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
|
return optionMap.value.get(value)?.label ?? value
|
||||||
}
|
}
|
||||||
|
|
||||||
function colorOf(value: string): string {
|
// ponytail: 简单确定性 hash → 8 色之一。已知 ceiling: hash 分布对短字符串
|
||||||
return optionMap.value.get(value)?.color ?? 'default'
|
// 可能聚集(不抗冲突),但 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:1(WCAG 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>
|
</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>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
/**
|
||||||
|
* useResponsiveBreakpoint — 响应式断点 composable
|
||||||
|
*
|
||||||
|
* 断点(移动优先,与 plan U1 Open Questions 一致):
|
||||||
|
* - isMobile: viewport < 768px
|
||||||
|
* - isTablet: 768px ≤ viewport < 1024px
|
||||||
|
* - isDesktop: viewport ≥ 1024px
|
||||||
|
* - isWide: viewport ≥ 1440px(供宽屏布局可选使用)
|
||||||
|
*
|
||||||
|
* 消费点:U3 RecordDetailDrawer(isMobile 时全屏覆盖)、
|
||||||
|
* U5 ViewConfigPanel(isMobile 时改底部抽屉)。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 互斥逻辑,不另立测试文件。
|
||||||
|
|
@ -2,6 +2,7 @@ import { createApp } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import 'ant-design-vue/dist/reset.css'
|
import 'ant-design-vue/dist/reset.css'
|
||||||
import './styles'
|
import './styles'
|
||||||
|
import './styles/bitable-tokens.css'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import { useAuthStore } from './stores/auth'
|
import { useAuthStore } from './stores/auth'
|
||||||
|
|
|
||||||
|
|
@ -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:1,WCAG 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;
|
||||||
|
}
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
|
|
||||||
<main class="bitable-file-detail-view__main">
|
<main class="bitable-file-detail-view__main">
|
||||||
<div v-if="!store.currentTable" class="bitable-file-detail-view__placeholder">
|
<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>
|
<p>请选择左侧的数据表,或点击 + 新建数据表</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -280,40 +280,40 @@ async function handleDeleteField(field: IBitableField): Promise<void> {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--bg-primary, #fff);
|
background: var(--bitable-color-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-file-detail-view__topbar {
|
.bitable-file-detail-view__topbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 8px 16px;
|
padding: var(--bitable-spacing-sm) var(--bitable-spacing-lg);
|
||||||
border-bottom: 1px solid var(--border-color, #f0f0f0);
|
border-bottom: 1px solid var(--bitable-color-border);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-file-detail-view__topbar-left {
|
.bitable-file-detail-view__topbar-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: var(--bitable-spacing-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-file-detail-view__topbar-right {
|
.bitable-file-detail-view__topbar-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: var(--bitable-spacing-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-file-detail-view__icon {
|
.bitable-file-detail-view__icon {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: var(--color-primary, #1a1a1a);
|
color: var(--bitable-color-primary);
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-file-detail-view__title {
|
.bitable-file-detail-view__title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 16px;
|
font-size: var(--bitable-font-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-file-detail-view__body {
|
.bitable-file-detail-view__body {
|
||||||
|
|
@ -342,28 +342,28 @@ async function handleDeleteField(field: IBitableField): Promise<void> {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
gap: 16px;
|
gap: var(--bitable-spacing-lg);
|
||||||
color: var(--text-placeholder, #bfbfbf);
|
color: var(--bitable-color-text-placeholder);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-file-detail-view__grid-header {
|
.bitable-file-detail-view__grid-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 12px;
|
gap: var(--bitable-spacing-md);
|
||||||
padding: 12px 16px;
|
padding: var(--bitable-spacing-md) var(--bitable-spacing-lg);
|
||||||
border-bottom: 1px solid var(--border-color, #f0f0f0);
|
border-bottom: 1px solid var(--bitable-color-border);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-file-detail-view__table-name {
|
.bitable-file-detail-view__table-name {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 16px;
|
font-size: var(--bitable-font-lg);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-file-detail-view__field-count {
|
.bitable-file-detail-view__field-count {
|
||||||
font-size: 12px;
|
font-size: var(--bitable-font-xs);
|
||||||
color: var(--text-secondary, #8c8c8c);
|
color: var(--bitable-color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-file-detail-view__grid-container {
|
.bitable-file-detail-view__grid-container {
|
||||||
|
|
@ -375,8 +375,8 @@ async function handleDeleteField(field: IBitableField): Promise<void> {
|
||||||
.bitable-file-detail-view__load-more {
|
.bitable-file-detail-view__load-more {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 8px;
|
padding: var(--bitable-spacing-sm);
|
||||||
border-top: 1px solid var(--border-color, #f0f0f0);
|
border-top: 1px solid var(--bitable-color-border);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -189,16 +189,16 @@ function handleDelete(file: IBitableFile): void {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--bg-secondary, #fafafa);
|
background: var(--bitable-color-bg-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-file-list-view__topbar {
|
.bitable-file-list-view__topbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 8px 16px;
|
padding: var(--bitable-spacing-sm) var(--bitable-spacing-lg);
|
||||||
background: var(--bg-primary, #fff);
|
background: var(--bitable-color-bg);
|
||||||
border-bottom: 1px solid var(--border-color, #f0f0f0);
|
border-bottom: 1px solid var(--bitable-color-border);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,7 +248,7 @@ function handleDelete(file: IBitableFile): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
.bitable-file-list-view__option-icon {
|
.bitable-file-list-view__option-icon {
|
||||||
font-size: 16px;
|
font-size: var(--bitable-font-lg);
|
||||||
color: var(--color-primary, #1a1a1a);
|
color: var(--bitable-color-primary);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue