Skip to content

Commit 8c1ef1b

Browse files
committed
feat(ui): optimize mobile ui
1 parent 8a9e784 commit 8c1ef1b

4 files changed

Lines changed: 168 additions & 76 deletions

File tree

ui/src/components/ResultTable.tsx

Lines changed: 150 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,143 @@
1-
import { Classes, Colors, HTMLTable } from '@blueprintjs/core'
1+
import { Colors } from '@blueprintjs/core'
22
import styled from '@emotion/styled'
33
import type { StringItem } from '@/search/interface'
44
import { highlightText } from '@/utils/highlight'
55
import { languageMap } from '@/utils/language'
66
import { useScrollIntoView } from '../utils/useScrollIntoView'
77

8-
const HighlightedTbody = styled.tbody({
9-
em: {
10-
textDecoration: 'none',
11-
backgroundColor: Colors.GOLD5,
12-
fontStyle: 'normal',
8+
const MOBILE_BREAKPOINT = 768
9+
10+
const ListContainer = styled.div({
11+
width: '100%',
12+
minWidth: 0,
13+
display: 'flex',
14+
flexDirection: 'column',
15+
})
16+
17+
const HeaderRow = styled.div({
18+
display: 'flex',
19+
flexDirection: 'row',
20+
borderBottom: '2px solid #e1e8ed',
21+
backgroundColor: '#f7f9fa',
22+
fontWeight: 600,
23+
fontSize: 12,
24+
color: '#5c7080',
25+
[`@media (max-width: ${MOBILE_BREAKPOINT - 1}px)`]: {
26+
display: 'none',
1327
},
14-
td: {
15-
whiteSpace: 'pre-wrap',
16-
wordBreak: 'break-word',
17-
cursor: 'auto',
28+
})
29+
30+
const HeaderCellPosition = styled.div({
31+
flex: '0 0 120px',
32+
minWidth: 0,
33+
padding: '10px 12px',
34+
borderRight: '1px solid #e1e8ed',
35+
})
36+
37+
const HeaderCellLang = styled.div({
38+
flex: 1,
39+
minWidth: 0,
40+
padding: '10px 12px',
41+
borderRight: '1px solid #e1e8ed',
42+
'&:last-of-type': {
43+
borderRight: 'none',
44+
},
45+
})
46+
47+
const ItemRow = styled.div<{ $highlight?: boolean }>(({ $highlight }) => ({
48+
display: 'flex',
49+
flexDirection: 'row',
50+
minWidth: 0,
51+
borderBottom: '1px solid #e1e8ed',
52+
'&:hover': {
53+
backgroundColor: Colors.LIGHT_GRAY4,
1854
},
19-
'td:nth-of-type(1)': {
20-
fontSize: 12,
21-
fontFamily: 'monospace',
55+
...($highlight && {
56+
backgroundColor: '#fef4a8',
57+
}),
58+
[`@media (max-width: ${MOBILE_BREAKPOINT - 1}px)`]: {
59+
flexDirection: 'column',
60+
border: '1px solid #e1e8ed',
61+
borderRadius: 8,
62+
marginBottom: 12,
63+
padding: 12,
64+
boxShadow: '0 1px 3px rgba(0,0,0,0.08)',
65+
'&:hover': {
66+
backgroundColor: 'transparent',
67+
},
68+
...($highlight && {
69+
backgroundColor: '#fef4a8',
70+
borderColor: '#d99e0b',
71+
}),
2272
},
23-
'.highlight-row td': {
24-
backgroundColor: `#fef4a8 !important`,
73+
}))
74+
75+
const CellPosition = styled.div({
76+
flex: '0 0 120px',
77+
minWidth: 0,
78+
padding: '10px 12px',
79+
borderRight: '1px solid #e1e8ed',
80+
fontSize: 12,
81+
fontFamily: 'monospace',
82+
whiteSpace: 'pre-wrap',
83+
wordBreak: 'break-word',
84+
[`@media (max-width: ${MOBILE_BREAKPOINT - 1}px)`]: {
85+
flex: 'none',
86+
width: '100%',
87+
borderRight: 'none',
88+
borderBottom: '1px solid #e1e8ed',
89+
marginBottom: 8,
90+
paddingTop: 0,
91+
paddingBottom: 8,
92+
paddingLeft: 0,
93+
paddingRight: 0,
2594
},
2695
})
2796

28-
const StyledHtmlTable = styled(HTMLTable)({
29-
minWidth: 700,
30-
width: '100%',
31-
tableLayout: 'fixed',
32-
[`&.${Classes.HTML_TABLE}.${Classes.HTML_TABLE_STRIPED} tbody tr:hover td`]: {
33-
backgroundColor: Colors.LIGHT_GRAY4,
97+
const CellLang = styled.div({
98+
flex: 1,
99+
minWidth: 0,
100+
padding: '10px 12px',
101+
borderRight: '1px solid #e1e8ed',
102+
whiteSpace: 'pre-wrap',
103+
wordBreak: 'break-word',
104+
fontSize: 14,
105+
'&:last-of-type': {
106+
borderRight: 'none',
34107
},
35-
borderCollapse: 'collapse',
36-
'th, tr, td': {
37-
border: '1px solid #fff',
108+
'& em': {
109+
textDecoration: 'none',
110+
backgroundColor: Colors.GOLD5,
111+
fontStyle: 'normal',
112+
},
113+
[`@media (max-width: ${MOBILE_BREAKPOINT - 1}px)`]: {
114+
display: 'flex',
115+
flexDirection: 'row',
116+
flex: 'none',
117+
width: '100%',
118+
borderRight: 'none',
119+
paddingLeft: 0,
120+
paddingRight: 0,
121+
paddingTop: 6,
122+
paddingBottom: 0,
123+
'&::before': {
124+
content: 'attr(data-label)',
125+
fontWeight: 600,
126+
display: 'inline-block',
127+
minWidth: 72,
128+
marginRight: 8,
129+
color: '#5c7080',
130+
},
38131
},
39132
})
40133

41134
const ScrollableContainer = styled.div({
42135
width: '100%',
136+
minWidth: 0,
43137
overflowX: 'auto',
138+
[`@media (max-width: ${MOBILE_BREAKPOINT - 1}px)`]: {
139+
overflowX: 'visible',
140+
},
44141
})
45142

46143
const LinkButton = styled.button({
@@ -50,6 +147,7 @@ const LinkButton = styled.button({
50147
backgroundColor: 'transparent',
51148
color: Colors.BLUE3,
52149
cursor: 'pointer',
150+
fontSize: 12,
53151
'&:hover': {
54152
textDecoration: 'underline',
55153
},
@@ -65,60 +163,49 @@ export interface IResultTableProps {
65163

66164
export function ResultTable(props: IResultTableProps) {
67165
const { items, keyword, displayLanguages } = props
68-
useScrollIntoView('.highlight-row', [props.highlightItem])
166+
useScrollIntoView('[data-highlight-row="true"]', [props.highlightItem])
69167

70168
return (
71169
<ScrollableContainer>
72-
<StyledHtmlTable striped>
73-
<colgroup>
74-
<col style={{ width: '120px' }} />
75-
<col
76-
span={displayLanguages.length}
77-
style={{
78-
width: `calc((100% - 120px) / ${displayLanguages.length})`,
79-
}}
80-
/>
81-
</colgroup>
82-
<thead>
83-
<tr>
84-
<th>位置</th>
85-
{displayLanguages.map((lang) => (
86-
<th key={lang}>
87-
{languageMap[lang as keyof typeof languageMap]}
88-
</th>
89-
))}
90-
</tr>
91-
</thead>
92-
<HighlightedTbody className={Classes.TEXT_LARGE}>
93-
{items.map((item, idx) => (
94-
<tr
170+
<ListContainer>
171+
<HeaderRow>
172+
<HeaderCellPosition>位置</HeaderCellPosition>
173+
{displayLanguages.map((lang) => (
174+
<HeaderCellLang key={lang}>
175+
{languageMap[lang as keyof typeof languageMap]}
176+
</HeaderCellLang>
177+
))}
178+
</HeaderRow>
179+
{items.map((item, idx) => {
180+
const isHighlight =
181+
item.sheet === props.highlightItem?.sheet &&
182+
item.rowId === props.highlightItem?.rowId
183+
return (
184+
<ItemRow
95185
key={`${item.sheet}-${item.rowId}-${idx}`}
96-
className={
97-
item.sheet === props.highlightItem?.sheet &&
98-
item.rowId === props.highlightItem?.rowId
99-
? 'highlight-row'
100-
: ''
101-
}
186+
$highlight={isHighlight}
187+
data-highlight-row={isHighlight ? 'true' : undefined}
102188
>
103-
<td>
189+
<CellPosition>
104190
{item.sheet}#{item.rowId}
105191
<br />
106192
<LinkButton onClick={() => props.onContextButtonClick?.(item)}>
107193
搜索上下文
108194
</LinkButton>
109-
</td>
195+
</CellPosition>
110196
{displayLanguages.map((lang) => {
111197
const value = item.values[lang]
198+
const label = languageMap[lang as keyof typeof languageMap]
112199
return (
113-
<td key={lang}>
114-
{value ? highlightText(value, keyword) : ''}
115-
</td>
200+
<CellLang key={lang} data-label={label}>
201+
<div>{value ? highlightText(value, keyword) : ''}</div>
202+
</CellLang>
116203
)
117204
})}
118-
</tr>
119-
))}
120-
</HighlightedTbody>
121-
</StyledHtmlTable>
205+
</ItemRow>
206+
)
207+
})}
208+
</ListContainer>
122209
</ScrollableContainer>
123210
)
124211
}

ui/src/components/SearchBar.tsx

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { Button } from '@blueprintjs/core'
22
import { MultiSelect, Select } from '@blueprintjs/select'
3+
import { css } from '@emotion/react'
34
import styled from '@emotion/styled'
45
import { type LanguageOption, languageOptions } from '@/utils/language'
56
import type { ISearchQuery } from '../search/useSearch'
67
import { type ISearchFieldProps, SearchField } from './SearchField'
7-
import { css } from '@emotion/react'
88

99
const Container = styled.div({
1010
display: 'flex',
@@ -156,12 +156,13 @@ function QueryLanguageSelect({
156156
}
157157

158158
export function SearchBar(props: ISearchBarProps) {
159-
const kw = props.previousQuery?.keyword?.keyword
159+
const { previousQuery } = props
160+
const kw = previousQuery?.keyword?.keyword
160161
const text = kw ? `返回搜索"${kw}"` : undefined
161162

162163
return (
163164
<Container>
164-
{props.previousQuery && (
165+
{previousQuery && (
165166
<Button
166167
onClick={() => props.onBackClicked?.()}
167168
text={text}
@@ -171,13 +172,17 @@ export function SearchBar(props: ISearchBarProps) {
171172
/>
172173
)}
173174
<SearchContainer>
174-
<QueryLanguageSelect
175-
value={props.language}
176-
onChange={props.onLanguageChange}
177-
/>
178-
<SearchInputContainer>
179-
<SearchField {...props} />
180-
</SearchInputContainer>
175+
{!previousQuery && (
176+
<>
177+
<QueryLanguageSelect
178+
value={props.language}
179+
onChange={props.onLanguageChange}
180+
/>
181+
<SearchInputContainer>
182+
<SearchField {...props} />
183+
</SearchInputContainer>
184+
</>
185+
)}
181186
<DisplayLanguagesSelect
182187
value={props.displayLanguages}
183188
onChange={props.onDisplayLanguagesChange}

ui/src/search/query.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ function processQuery(searchParams: URLSearchParams, q: string) {
1818
const parts = q.split(' ')
1919

2020
// Extract sheet from query
21-
const sheetPart = parts.findIndex(part => part.startsWith(sheetPrefix))
21+
const sheetPart = parts.findIndex((part) => part.startsWith(sheetPrefix))
2222
if (sheetPart !== -1) {
2323
searchParams.set('sheet', parts[sheetPart].slice(sheetPrefix.length))
2424
parts.splice(sheetPart, 1)
2525
}
2626

27-
searchParams.set('q', parts.filter(part => !!part).join(' '))
27+
searchParams.set('q', parts.filter((part) => !!part).join(' '))
2828
}
2929

3030
export async function searchApi(

ui/src/utils/useScrollIntoView.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export function useScrollIntoView(selector: string, deps: unknown[]) {
77
const dom = document.querySelector(selector)
88
if (dom) {
99
document.scrollingElement?.scrollTo({
10-
top: (dom as HTMLElement).offsetTop,
10+
top: (dom as HTMLElement).offsetTop - 100,
1111
behavior: 'smooth',
1212
})
1313
}

0 commit comments

Comments
 (0)