Merge pull request #35 from guanweiwang/main

feat: 优化代码补全展示,添加筛选
This commit is contained in:
Yoko
2025-07-03 11:34:17 +08:00
committed by GitHub
9 changed files with 483 additions and 245 deletions

View File

@@ -14,6 +14,7 @@
"@c-x/ui": "^1.0.9",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@monaco-editor/react": "4.7.0-rc.0",
"@mui/icons-material": "^6.4.12",
"@mui/lab": "6.0.0-beta.19",
"@mui/material": "^6.4.12",

34
ui/pnpm-lock.yaml generated
View File

@@ -17,6 +17,9 @@ importers:
'@emotion/styled':
specifier: ^11.14.0
version: 11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
'@monaco-editor/react':
specifier: 4.7.0-rc.0
version: 4.7.0-rc.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@mui/icons-material':
specifier: ^6.4.12
version: 6.4.12(@mui/material@6.4.12(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
@@ -548,6 +551,16 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
'@monaco-editor/loader@1.5.0':
resolution: {integrity: sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==}
'@monaco-editor/react@4.7.0-rc.0':
resolution: {integrity: sha512-YfjXkDK0bcwS0zo8PXptvQdCQfOPPtzGsAzmIv7PnoUGFdIohsR+NVDyjbajMddF+3cWUm/3q9NzP/DUke9a+w==}
peerDependencies:
monaco-editor: '>= 0.25.0 < 1'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@mui/base@5.0.0-beta.66':
resolution: {integrity: sha512-1SzcNbtIms0o/Dx+599B6QbvR5qUMBUjwc2Gs47h1HsF7RcEFXxqaq7zrWkIWbvGctIIPx0j330oGx/SkF+UmA==}
engines: {node: '>=14.0.0'}
@@ -1836,6 +1849,9 @@ packages:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
monaco-editor@0.52.2:
resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -2203,6 +2219,9 @@ packages:
space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
state-local@1.0.7:
resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -2843,6 +2862,17 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
'@monaco-editor/loader@1.5.0':
dependencies:
state-local: 1.0.7
'@monaco-editor/react@4.7.0-rc.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@monaco-editor/loader': 1.5.0
monaco-editor: 0.52.2
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
'@mui/base@5.0.0-beta.66(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@babel/runtime': 7.27.6
@@ -4363,6 +4393,8 @@ snapshots:
dependencies:
brace-expansion: 2.0.2
monaco-editor@0.52.2: {}
ms@2.1.3: {}
nanoid@3.3.11: {}
@@ -4783,6 +4815,8 @@ snapshots:
space-separated-tokens@2.0.2: {}
state-local@1.0.7: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0

View File

@@ -131,6 +131,7 @@ export interface DomainCompletionInfo {
content?: string;
created_at?: number;
id?: string;
prompt?: string;
}
export interface DomainCompletionRecord {
@@ -330,11 +331,13 @@ export interface DomainProviderModel {
export interface DomainRegisterReq {
/** 邀请码 */
code?: string;
code: string;
/** 邮箱 */
email?: string;
email: string;
/** 密码 */
password?: string;
password: string;
/** 用户名 */
username: string;
}
export interface DomainSetting {
@@ -585,6 +588,12 @@ export interface GetChatInfoParams {
}
export interface GetListChatRecordParams {
/** 作者 */
author?: string;
/** 是否接受筛选 */
is_accept?: boolean;
/** 语言 */
language?: string;
/** 下一页标识 */
next_token?: string;
/** 分页 */
@@ -599,6 +608,12 @@ export interface GetCompletionInfoParams {
}
export interface GetListCompletionRecordParams {
/** 作者 */
author?: string;
/** 是否接受筛选 */
is_accept?: boolean;
/** 语言 */
language?: string;
/** 下一页标识 */
next_token?: string;
/** 分页 */

File diff suppressed because one or more lines are too long

View File

@@ -1,16 +1,10 @@
import Avatar from '@/components/avatar';
import Card from '@/components/card';
import { getChatInfo } from '@/api/Billing';
import MarkDown from '@/components/markDown';
import { addCommasToNumber, processText } from '@/utils';
import { Ellipsis, Icon, Modal } from '@c-x/ui';
import { Box, Stack, Tooltip, useTheme } from '@mui/material';
import dayjs from 'dayjs';
import { useEffect, useState } from 'react';
import { DomainCompletionRecord } from '@/api/types';
import { getCompletionInfo } from '@/api/Billing';
import { Modal } from '@c-x/ui';
import MonacoEditor from '@monaco-editor/react';
type ConversationItem = any;
type ToolInfo = any;
import { useEffect, useState, useRef } from 'react';
import { DomainCompletionRecord } from '@/api/types';
const ChatDetailModal = ({
data,
@@ -21,51 +15,77 @@ const ChatDetailModal = ({
open: boolean;
onClose: () => void;
}) => {
const theme = useTheme();
const [ChatDetailModal, setChatDetailModal] =
useState<ConversationItem | null>(null);
const [content, setContent] = useState<string>('');
const [showToolInfo, setShowToolInfo] = useState<{ [key: string]: ToolInfo }>(
{}
);
const [editorValue, setEditorValue] = useState<string>('');
const editorRef = useRef<any>(null);
const [editorReady, setEditorReady] = useState(false);
const [highlightInfo, setHighlightInfo] = useState<any>(null);
const getChatDetailModal = () => {
if (!data) return;
getChatInfo({ id: data.id! }).then((res) => {
setContent(
data.program_language
? `\`\`\`${data.program_language}\n${res.content || ''}\n\`\`\``
: res.content || ''
);
getCompletionInfo({ id: data.id! }).then((res) => {
const rawPrompt = res.prompt || '';
const content = res.content || '';
// 找到三个特殊标记的位置
const prefixTag = '<|fim_prefix|>';
const suffixTag = '<|fim_suffix|>';
const middleTag = '<|fim_middle|>';
const prefixIdx = rawPrompt.indexOf(prefixTag);
const suffixIdx = rawPrompt.indexOf(suffixTag);
const middleIdx = rawPrompt.indexOf(middleTag);
// 去掉特殊标记
const prompt = rawPrompt
.replace(prefixTag, '')
.replace(suffixTag, '')
.replace(middleTag, '');
// 重新定位插入点(因为去掉了前面的 tag位置会变
// 计算插入点suffixTag 在原始 prompt 的位置,去掉 prefixTag 后的 offset
let insertIdx = suffixIdx;
if (prefixIdx !== -1 && prefixIdx < suffixIdx) {
insertIdx -= prefixTag.length;
}
if (middleIdx !== -1 && middleIdx < suffixIdx) {
insertIdx -= middleTag.length;
}
// 插入 content
const newValue =
prompt.slice(0, insertIdx) + content + prompt.slice(insertIdx);
setEditorValue(newValue);
// 计算高亮范围(行列)
const before = newValue.slice(0, insertIdx);
const contentLines = content.split('\n');
const beforeLines = before.split('\n');
const startLine = beforeLines.length;
const startColumn = beforeLines[beforeLines.length - 1].length + 1;
const endLine = startLine + contentLines.length - 1;
const endColumn =
contentLines.length === 1
? startColumn + content.length
: contentLines[contentLines.length - 1].length + 1;
setHighlightInfo({ startLine, startColumn, endLine, endColumn });
});
// getConversationChatDetailModal({ id }).then((res) => {
// const newAnswer = res.answer
// const toolWrapsIds = newAnswer.match(/<tools id="([^"]+)">/g)?.map(match => {
// const idMatch = match.match(/<tools id="([^"]+)">/);
// return idMatch ? idMatch[1] : null;
// }).filter(Boolean) || [];
// const toolIds = newAnswer.match(/<tool id="([^"]+)">/g)?.map(match => {
// const idMatch = match.match(/<tool id="([^"]+)">/);
// return idMatch ? idMatch[1] : null;
// }).filter(Boolean) || [];
// const obj: { [key: string]: ToolInfo } = {}
// toolWrapsIds.forEach(id => {
// obj[id!] = {
// done: true,
// }
// })
// toolIds.forEach(id => {
// obj[id!] = {
// args: false,
// result: false,
// done: true,
// }
// })
// setShowToolInfo(obj)
// setChatDetailModal({ ...res, answer: processText(res.answer) })
// })
};
useEffect(() => {
if (editorReady && highlightInfo && editorRef.current) {
editorRef.current.deltaDecorations(
[],
[
{
range: {
startLineNumber: highlightInfo.startLine,
startColumn: highlightInfo.startColumn,
endLineNumber: highlightInfo.endLine,
endColumn: highlightInfo.endColumn,
},
options: {
inlineClassName: 'completion-highlight',
},
},
]
);
}
}, [editorReady, highlightInfo, editorValue]);
useEffect(() => {
if (open) getChatDetailModal();
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -79,164 +99,71 @@ const ChatDetailModal = ({
onCancel={onClose}
footer={null}
>
{ChatDetailModal ? (
<Box sx={{ fontSize: 14 }}>
<Stack
direction={'row'}
alignItems={'center'}
gap={3}
sx={{
<Card sx={{ p: 0 }}>
<div style={{ height: 420 }}>
<MonacoEditor
height='100%'
language={data?.program_language || 'plaintext'}
value={editorValue}
theme='vs-dark'
options={{
readOnly: true,
minimap: { enabled: false },
fontSize: 14,
color: 'text.auxiliary',
}}
>
{ChatDetailModal.created_at && (
<Stack direction={'row'} alignItems={'center'} gap={1}>
<Icon type='icon-a-shijian2' />
{dayjs(ChatDetailModal.created_at).format(
'YYYY-MM-DD HH:mm:ss'
)}
</Stack>
)}
{ChatDetailModal.remote_ip && (
<Stack direction={'row'} alignItems={'center'} gap={1}>
<Icon type='icon-IPdizhijiancha' />
{ChatDetailModal.remote_ip}
</Stack>
)}
{ChatDetailModal.model && (
<Stack direction={'row'} alignItems={'center'} gap={1}>
<Icon type='icon-moxing' />
使
<Box>{ChatDetailModal.model}</Box>
</Stack>
)}
{data?.input_tokens && data?.output_tokens && (
<Tooltip
title={
<Stack gap={1} sx={{ minWidth: 100, py: 1 }}>
<Box>
Token 使 {addCommasToNumber(data?.input_tokens)}
</Box>
<Box>
Token 使 {addCommasToNumber(data?.output_tokens)}
</Box>
</Stack>
}
>
<Stack
direction={'row'}
alignItems={'center'}
gap={1}
sx={{ cursor: 'pointer' }}
>
<Icon type='icon-moxing' />
Token
<Box>
{addCommasToNumber(
data?.input_tokens + data?.output_tokens
)}
</Box>
<Icon type='icon-a-wenhao8' />
</Stack>
</Tooltip>
)}
</Stack>
{ChatDetailModal.references?.length > 0 && (
<>
<Stack
direction={'row'}
alignItems={'center'}
gap={1}
sx={{
fontWeight: 'bold',
mt: 2,
mb: 1,
'&::before': {
content: '""',
display: 'inline-block',
width: '4px',
height: '12px',
borderRadius: '2px',
backgroundColor: theme.palette.primary.main,
},
}}
>
</Stack>
<Card sx={{ p: 2, bgcolor: 'background.paper2' }}>
{ChatDetailModal.references.map((item: any, index: number) => (
<Stack
direction={'row'}
alignItems={'center'}
gap={1}
key={index}
>
<Avatar
src={item.favicon}
sx={{ width: 18, height: 18 }}
errorIcon={
<Icon
type='icon-ditu_diqiu'
sx={{ fontSize: 18, color: 'text.auxiliary' }}
/>
}
/>
<Ellipsis>
<Box
component={'a'}
href={item.url}
target='_blank'
sx={{
color: 'text.primary',
'&:hover': { color: 'primary.main' },
}}
>
{item.title}
</Box>
</Ellipsis>
</Stack>
))}
</Card>
</>
)}
<Stack
direction={'row'}
alignItems={'center'}
gap={1}
sx={{
fontWeight: 'bold',
mt: 2,
mb: 1,
'&::before': {
content: '""',
display: 'inline-block',
width: '4px',
height: '12px',
borderRadius: '2px',
backgroundColor: theme.palette.primary.main,
scrollBeyondLastLine: false,
wordWrap: 'on',
lineNumbers: 'on',
glyphMargin: false,
folding: false,
scrollbar: {
vertical: 'hidden',
horizontal: 'hidden',
handleMouseWheel: false,
alwaysConsumeMouseWheel: false,
useShadows: false,
},
overviewRulerLanes: 0,
guides: {
indentation: true,
highlightActiveIndentation: true,
highlightActiveBracketPair: false,
},
renderLineHighlight: 'none',
cursorStyle: 'line',
cursorBlinking: 'solid',
cursorWidth: 0,
contextmenu: false,
selectionHighlight: false,
selectOnLineNumbers: false,
occurrencesHighlight: 'off',
links: false,
hover: { enabled: false },
codeLens: false,
dragAndDrop: false,
mouseWheelZoom: false,
accessibilitySupport: 'off',
bracketPairColorization: { enabled: false },
matchBrackets: 'never',
}}
>
</Stack>
</Box>
) : (
<Box></Box>
)}
<Card
sx={{
'.markdown-body': {
background: 'transparent',
},
p: 0,
}}
>
<MarkDown
showToolInfo={showToolInfo}
setShowToolInfo={setShowToolInfo}
content={content}
/>
onMount={(editor) => {
editorRef.current = editor;
setEditorReady(true);
// 隐藏光标
const editorDom = editor.getDomNode();
if (editorDom) {
const style = document.createElement('style');
style.innerHTML = `.monaco-editor .cursor { display: none !important; }`;
editorDom.appendChild(style);
}
}}
/>
</div>
<style>{`
.completion-highlight {
background: #264f78 !important;
transition: background 0.2s;
}
`}</style>
</Card>
</Modal>
);

View File

@@ -0,0 +1,67 @@
export const LANG_OPTIONS = [
'JavaScript',
'JavaScriptReact',
'TypeScript',
'TypeScriptReact',
'Python',
'Java',
'C',
'C++',
'C#',
'Go',
'PHP',
'Ruby',
'Swift',
'Kotlin',
'Rust',
'Dart',
'Objective-C',
'Scala',
'Perl',
'R',
'Shell Script',
'PowerShell',
'HTML',
'CSS',
'SCSS',
'Less',
'JSON',
'YAML',
'XML',
'Markdown',
'SQL',
'GraphQL',
'Dockerfile',
'Makefile',
'Lua',
'Haskell',
'Elixir',
'Erlang',
'F#',
'Groovy',
'Visual Basic',
'Assembly',
'Matlab',
'Fortran',
'COBOL',
'Prolog',
'Scheme',
'Lisp',
'Julia',
'SASS',
'TOML',
'INI',
'LaTeX',
'CMake',
'Batch',
'CoffeeScript',
'Crystal',
'OCaml',
'Nim',
'ReScript',
'Solidity',
'Vue',
'Svelte',
'JSX',
'TSX',
];

View File

@@ -1,15 +1,54 @@
import React, { useState, useEffect } from 'react';
import React, {
useState,
useEffect,
useMemo,
useCallback,
useRef,
} from 'react';
import { DomainCompletionRecord } from '@/api/types';
import { getListCompletionRecord } from '@/api/Billing';
import { useRequest } from 'ahooks';
import { Table } from '@c-x/ui';
import Card from '@/components/card';
import { Box, Button, ButtonBase, Chip, Stack, alpha } from '@mui/material';
import {
Box,
Button,
ButtonBase,
Chip,
Stack,
alpha,
MenuItem,
Select,
FormControl,
InputLabel,
Autocomplete,
TextField,
InputAdornment,
} from '@mui/material';
import { getListUser } from '@/api/User';
import dayjs from 'dayjs';
import { ColumnsType } from '@c-x/ui/dist/Table';
import { addCommasToNumber } from '@/utils';
import CompletionDetailModal from './completionDetailModal';
import StyledLabel from '@/components/label';
import { LANG_OPTIONS } from './constant';
import { ArrowDropDown as ArrowDropDownIcon } from '@mui/icons-material';
// 防抖 hook
function useDebounce(fn: (...args: any[]) => void, delay: number) {
const timer = useRef<number | null>(null);
const fnRef = useRef(fn);
fnRef.current = fn;
return useCallback(
(...args: any[]) => {
if (timer.current) clearTimeout(timer.current);
timer.current = setTimeout(() => {
fnRef.current(...args);
}, delay);
},
[delay]
);
}
const Completion = () => {
const [page, setPage] = useState(1);
@@ -20,26 +59,60 @@ const Completion = () => {
const [completionDetailModal, setCompletionDetailModal] = useState<
DomainCompletionRecord | undefined
>();
const fetchData = async () => {
// 新增筛选项 state
const [filterUser, setFilterUser] = useState('');
const [filterLang, setFilterLang] = useState('');
const [filterAccept, setFilterAccept] = useState<
'accepted' | 'unaccepted' | ''
>('');
const { data: userOptions = { users: [] } } = useRequest(() =>
getListUser({
page: 1,
size: 99999,
})
);
useEffect(() => {
setPage(1); // 筛选变化时重置页码
fetchData({
page: 1,
language: filterLang,
author: filterUser,
is_accept: filterAccept,
});
}, [filterUser, filterLang, filterAccept]);
const fetchData = async (params: {
page?: number;
size?: number;
language?: string;
author?: string;
is_accept?: 'accepted' | 'unaccepted' | '';
}) => {
setLoading(true);
const res = await getListCompletionRecord({
page: page,
size: size,
page: params.page || page,
size: params.size || size,
language: params.language || filterLang,
author: params.author || filterUser,
is_accept:
params.is_accept === 'accepted'
? true
: params.is_accept === 'unaccepted'
? false
: undefined,
});
setLoading(false);
setTotal(res?.total_count || 0);
setDataSource(res.records || []);
};
useEffect(() => {
fetchData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [page, size]);
const columns: ColumnsType<DomainCompletionRecord> = [
{
dataIndex: 'user',
title: '成员',
render(value: DomainCompletionRecord['user']) {
return value?.username;
},
@@ -60,7 +133,6 @@ const Completion = () => {
);
},
},
{
dataIndex: 'is_accept',
title: '是否采纳',
@@ -72,7 +144,6 @@ const Completion = () => {
);
},
},
{
dataIndex: 'program_language',
title: '编程语言',
@@ -88,7 +159,6 @@ const Completion = () => {
{
dataIndex: 'output_tokens',
title: '输出 Token',
render(value: number) {
return addCommasToNumber(value);
},
@@ -102,11 +172,72 @@ const Completion = () => {
},
},
];
const debounceSetFilterLang = useDebounce(
(val: string) => setFilterLang(val),
500
);
return (
<Card sx={{ flex: 1, height: '100%' }}>
{/* 筛选项 */}
<Stack direction='row' spacing={2} sx={{ mb: 2 }}>
{/* 成员筛选 Autocomplete */}
<Autocomplete
size='small'
sx={{ minWidth: 220 }}
options={userOptions.users || []}
getOptionLabel={(option) => option.username || ''}
value={
userOptions.users?.find((item) => item.username === filterUser) ||
null
}
onChange={(_, newValue) =>
setFilterUser(newValue ? newValue.username! : '')
}
isOptionEqualToValue={(option, value) =>
option.username === value.username
}
renderInput={(params) => <TextField {...params} label='成员' />}
clearOnEscape
/>
{/* 语言筛选 Autocomplete */}
<Autocomplete
size='small'
sx={{ minWidth: 220 }}
options={LANG_OPTIONS}
getOptionLabel={(option) => option || ''}
value={filterLang || ''}
freeSolo
onChange={(_, newValue) => {
setFilterLang(newValue ? String(newValue) : '');
}}
onInputChange={(_, newInputValue) =>
debounceSetFilterLang(newInputValue)
}
popupIcon={<ArrowDropDownIcon />}
renderInput={(params) => <TextField {...params} label='语言' />}
clearOnEscape
/>
{/* 是否采纳筛选 Select 保持不变 */}
<FormControl size='small' sx={{ minWidth: 180 }}>
<InputLabel></InputLabel>
<Select
label='是否采纳'
value={filterAccept}
onChange={(e) =>
setFilterAccept(e.target.value as 'accepted' | 'unaccepted')
}
>
<MenuItem value=''></MenuItem>
<MenuItem value='accepted'></MenuItem>
<MenuItem value='unaccepted'></MenuItem>
</Select>
</FormControl>
</Stack>
<Table
size='large'
height='100%'
height='calc(100% - 56px)'
loading={loading}
columns={columns}
dataSource={dataSource || []}
@@ -125,6 +256,10 @@ const Completion = () => {
onChange: (page, size) => {
setPage(page);
setSize(size);
fetchData({
page,
size,
});
},
}}
/>

View File

@@ -50,7 +50,7 @@ const StyledPaper = styled(Paper)(({ theme }) => ({
padding: theme.spacing(4),
background: 'rgba(255, 255, 255, 0.85)',
backdropFilter: 'blur(10px)',
width: 600,
width: 500,
borderRadius: theme.spacing(2),
boxShadow:
'0px 0px 4px 0px rgba(54,59,76,0.1), 0px 20px 40px 0px rgba(54,59,76,0.1)',
@@ -67,6 +67,27 @@ const StepCard = styled(Box)(({ theme }) => ({
borderRadius: theme.spacing(2),
}));
const IconWrapper = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: theme.palette.text.primary,
marginRight: theme.spacing(2),
fontSize: 16,
}));
const StyledTextField = styled(TextField)(({ theme }) => ({
'.MuiInputBase-root': {
backgroundColor: '#fff',
paddingLeft: '20px',
},
'.MuiInputBase-input': {
paddingTop: '16px',
paddingBottom: '16px',
fontSize: 14,
},
}));
const Invite = () => {
const { id, step } = useParams();
const [showPassword, setShowPassword] = useState(false);
@@ -79,6 +100,7 @@ const Invite = () => {
defaultValues: {
email: '',
password: '',
username: '',
},
});
@@ -92,7 +114,7 @@ const Invite = () => {
};
const onRegister = handleSubmit((data) => {
register({ ...data, code: id }).then(() => {
register({ ...data, code: id! }).then(() => {
onNext();
});
});
@@ -127,6 +149,35 @@ const Invite = () => {
return !loginSetting.enable_dingtalk_oauth ? (
<Box component='form' onSubmit={onRegister}>
<Grid container spacing={3}>
<Grid size={12}>
<Controller
name='username'
control={control}
rules={{
required: '请输入用户名',
}}
render={({ field }) => (
<StyledTextField
{...field}
fullWidth
placeholder='请输入您的用户名'
variant='outlined'
error={!!errors.username}
helperText={errors.username?.message}
disabled={loading}
slotProps={{
input: {
startAdornment: (
<IconWrapper>
<Icon type='icon-zhanghao' />
</IconWrapper>
),
},
}}
/>
)}
/>
</Grid>
<Grid size={12}>
<Controller
name='email'
@@ -139,7 +190,7 @@ const Invite = () => {
},
}}
render={({ field }) => (
<TextField
<StyledTextField
{...field}
fullWidth
placeholder='请输入您的邮箱地址'
@@ -150,14 +201,9 @@ const Invite = () => {
slotProps={{
input: {
startAdornment: (
<Icon
type='icon-youxiang'
sx={{
color: 'text.primary',
mr: 1,
fontSize: 18,
}}
/>
<IconWrapper>
<Icon type='icon-youxiang' />
</IconWrapper>
),
},
}}
@@ -178,7 +224,7 @@ const Invite = () => {
},
}}
render={({ field }) => (
<TextField
<StyledTextField
{...field}
fullWidth
placeholder='请设置您的密码'
@@ -190,14 +236,9 @@ const Invite = () => {
slotProps={{
input: {
startAdornment: (
<Icon
type='icon-mima'
sx={{
color: 'text.primary',
mr: 1,
fontSize: 18,
}}
/>
<IconWrapper>
<Icon type='icon-mima' />
</IconWrapper>
),
endAdornment: (
<InputAdornment position='end'>
@@ -349,7 +390,7 @@ const Invite = () => {
</LogoContainer>
<Stepper
activeStep={activeStep}
activeStep={activeStep - 1}
alternativeLabel
sx={{
mb: 4,

View File

@@ -1,5 +1,5 @@
'use client';
import { createTheme } from '@mui/material';
import { createTheme, Paper } from '@mui/material';
import type { Shadows } from '@mui/material';
import { zhCN } from '@mui/material/locale';
import { zhCN as CuiZhCN } from '@c-x/ui/dist/local';
@@ -141,6 +141,24 @@ const lightTheme = createTheme(
},
},
},
MuiAutocomplete: {
defaultProps: {
slotProps: {
paper: {
elevation: 8,
},
},
},
styleOverrides: {
paper: {
borderRadius: '10px',
},
option: {
fontSize: '14px',
fontFamily: 'var(--font-gilory), var(--font-HarmonyOS)',
},
},
},
MuiFormLabel: {
styleOverrides: {
root: {