chore: custom brand color

This commit is contained in:
jialin
2025-04-28 10:05:06 +08:00
parent 3fb3381a19
commit fb90d79eb3
12 changed files with 398 additions and 139 deletions
+5 -1
View File
@@ -1,14 +1,17 @@
import { colorPrimary } from '@/config/theme';
import { atom } from 'jotai'; import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils'; import { atomWithStorage } from 'jotai/utils';
type UserSettings = { type UserSettings = {
theme: 'light' | 'realDark' | 'auto'; theme: 'light' | 'realDark' | 'auto';
colorPrimary: string;
isDarkTheme?: boolean; isDarkTheme?: boolean;
}; };
export const userSettingsAtom = atomWithStorage<UserSettings>('userSettings', { export const userSettingsAtom = atomWithStorage<UserSettings>('userSettings', {
theme: 'light', theme: 'light',
isDarkTheme: false isDarkTheme: false,
colorPrimary: colorPrimary
}); });
export const userSettingsHelperAtom = atom( export const userSettingsHelperAtom = atom(
@@ -21,6 +24,7 @@ export const userSettingsHelperAtom = atom(
}; };
set(userSettingsAtom, { set(userSettingsAtom, {
...newSettings, ...newSettings,
colorPrimary: newSettings.colorPrimary || colorPrimary,
isDarkTheme: newSettings.theme === 'realDark' isDarkTheme: newSettings.theme === 'realDark'
}); });
} }
+17 -1
View File
@@ -1,3 +1,4 @@
import { theme } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import React, { FC } from 'react'; import React, { FC } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -191,6 +192,8 @@ const Wrapper: FC<WrapperProps> = ({
labelExtra, labelExtra,
onClick onClick
}) => { }) => {
const { token } = theme.useToken();
console.log('token=====', token);
const wrapperClass = classNames( const wrapperClass = classNames(
status ? `validate-status-${status}` : '', status ? `validate-status-${status}` : '',
className, className,
@@ -201,8 +204,21 @@ const Wrapper: FC<WrapperProps> = ({
'seal-input-wrapper-disabled': disabled 'seal-input-wrapper-disabled': disabled
} }
); );
const wrapperStyle: Record<string, any> = {
'--ant-line-width': '1px',
'--ant-line-type': 'solid',
'--ant-color-border': token.colorBorder,
'--ant-color-bg-container': token.colorBgContainer,
'--ant-color-bg-container-disabled': token.colorBgContainerDisabled,
'--ant-color-error': token.colorError,
'--ant-input-hover-border-color': token.colorPrimaryHover,
'--ant-color-error-border-hover': token.colorErrorBorderHover,
'--ant-input-active-border-color': token.colorPrimary,
'--ant-input-active-shadow': `0 0 0 2px ${token.controlOutline}`,
'--ant-input-active-bg': token.colorBgContainer
};
return ( return (
<WrapperBox className={wrapperClass}> <WrapperBox className={wrapperClass} style={wrapperStyle}>
<InnerWrapper <InnerWrapper
$noWrapperStyle={noWrapperStyle} $noWrapperStyle={noWrapperStyle}
$nolabel={!label} $nolabel={!label}
+1 -1
View File
@@ -73,7 +73,7 @@ export default {
"Helvetica Neue, -apple-system, BlinkMacSystemFont, Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'", "Helvetica Neue, -apple-system, BlinkMacSystemFont, Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'",
colorText: '#ccc', colorText: '#ccc',
colorPrimary: '#007BFF', colorPrimary: '#007BFF',
// colorSuccess: '#48A77E', colorSuccess: '#48A77E',
borderRadius: 4, borderRadius: 4,
borderRadiusSM: 2, borderRadiusSM: 2,
fontSize: 14, fontSize: 14,
+3 -1
View File
@@ -1,7 +1,9 @@
import dark from './dark'; import dark from './dark';
import light from './light'; import light from './light';
export const colorPrimary = '#007BFF';
export default { export default {
light, light,
dark dark,
colorPrimary: '#007BFF'
}; };
+8 -8
View File
@@ -155,15 +155,15 @@ body {
} }
// input // input
.ant-input-css-var.ant-input { // .ant-input-css-var.ant-input {
height: 40px; // height: 40px;
} // }
.ant-input-outlined, // .ant-input-outlined,
.ant-input-affix-wrapper, // .ant-input-affix-wrapper,
.ant-picker { // .ant-picker {
height: 40px; // height: 40px;
} // }
.ant-input-outlined.ant-input-status-error:not(.ant-input-disabled) { .ant-input-outlined.ant-input-status-error:not(.ant-input-disabled) {
border: none; border: none;
+2 -1
View File
@@ -1,7 +1,8 @@
import { getActiveStatus, setActiveStatus } from '@/atoms/tab-active'; import { getActiveStatus, setActiveStatus } from '@/atoms/tab-active';
const tabsMap = { const tabsMap = {
resources: 'resources' resources: 'resources',
userSettings: 'userSettings'
}; };
export default function useTabActive() { export default function useTabActive() {
+5 -5
View File
@@ -6,7 +6,7 @@ import { useEffect, useMemo } from 'react';
type Theme = 'light' | 'realDark' | 'auto'; type Theme = 'light' | 'realDark' | 'auto';
export default function useUserSettings() { export default function useUserSettings() {
const { light, dark } = themeConfig; const { light, dark, colorPrimary } = themeConfig;
const [userSettings, setUserSettings] = useAtom(userSettingsHelperAtom); const [userSettings, setUserSettings] = useAtom(userSettingsHelperAtom);
const setHtmlThemeAttr = (theme: string) => { const setHtmlThemeAttr = (theme: string) => {
@@ -17,15 +17,16 @@ export default function useUserSettings() {
}; };
const themeData = useMemo(() => { const themeData = useMemo(() => {
const themeTokens = userSettings.theme === 'realDark' ? dark : light;
themeTokens.token.colorPrimary = userSettings.colorPrimary || colorPrimary;
return { return {
...(userSettings.theme === 'realDark' ? dark : light) ...themeTokens
}; };
}, [userSettings.theme]); }, [userSettings.theme, userSettings.colorPrimary]);
const setTheme = (theme: Theme) => { const setTheme = (theme: Theme) => {
setHtmlThemeAttr(theme); setHtmlThemeAttr(theme);
setUserSettings({ setUserSettings({
...userSettings,
theme: theme, theme: theme,
isDarkTheme: theme === 'realDark' isDarkTheme: theme === 'realDark'
}); });
@@ -35,7 +36,6 @@ export default function useUserSettings() {
setHtmlThemeAttr(userSettings.theme); setHtmlThemeAttr(userSettings.theme);
}, [userSettings.theme]); }, [userSettings.theme]);
// set theme by system theme: only for theme is auto
useEffect(() => { useEffect(() => {
const handleChange = (e: MediaQueryListEvent) => { const handleChange = (e: MediaQueryListEvent) => {
setTheme(e.matches ? 'realDark' : 'light'); setTheme(e.matches ? 'realDark' : 'light');
+12
View File
@@ -407,9 +407,21 @@ export default (props: any) => {
); );
}; };
const currentTheme = useMemo(() => {
const data = {
algorithm: userSettings.isDarkTheme
? theme.darkAlgorithm
: theme.defaultAlgorithm,
...themeData
};
console.log('currentTheme====', data);
return data;
}, [userSettings.isDarkTheme, themeData]);
return ( return (
<ConfigProvider <ConfigProvider
componentSize="large" componentSize="large"
key={userSettings.colorPrimary}
theme={{ theme={{
algorithm: userSettings.isDarkTheme algorithm: userSettings.isDarkTheme
? theme.darkAlgorithm ? theme.darkAlgorithm
+135
View File
@@ -0,0 +1,135 @@
import FormButtons from '@/components/form-buttons';
import SealInput from '@/components/seal-form/seal-input';
import { PasswordReg } from '@/config';
import { INPUT_WIDTH } from '@/constants';
import { updatePassword } from '@/pages/login/apis';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import { Form, message } from 'antd';
import React from 'react';
interface ProfileProps {
username?: string;
new_password: string;
current_password: string;
}
const Profile: React.FC = () => {
const [form] = Form.useForm();
const intl = useIntl();
const handleOnFinish = async (values: any) => {
try {
await updatePassword({
new_password: values.new_password,
current_password: values.current_password
});
message.success(intl.formatMessage({ id: 'common.message.success' }));
} catch (error) {}
};
const handleOnFinishFailed = (errorInfo: any) => {
console.log('handleOnFinishFailed', errorInfo);
};
const handleCancel = () => {
form.resetFields();
};
return (
<PageContainer
ghost
header={{
title: intl.formatMessage({ id: 'users.form.updatepassword' }),
style: {
paddingInline: 'var(--layout-content-header-inlinepadding)'
}
}}
extra={[]}
>
<Form
style={{ width: '524px' }}
name="profileForm"
form={form}
onFinish={handleOnFinish}
onFinishFailed={handleOnFinishFailed}
>
<Form.Item<ProfileProps>
name="current_password"
rules={[
{
required: true,
message: intl.formatMessage(
{ id: 'common.form.rule.input' },
{
name: intl.formatMessage({
id: 'users.form.currentpassword'
})
}
)
}
]}
>
<SealInput.Password
label={intl.formatMessage({ id: 'users.form.currentpassword' })}
required
style={{ width: INPUT_WIDTH.default }}
></SealInput.Password>
</Form.Item>
<Form.Item<ProfileProps>
name="new_password"
rules={[
{
required: true,
pattern: PasswordReg,
message: intl.formatMessage({
id: 'users.form.rule.password'
})
}
]}
>
<SealInput.Password
label={intl.formatMessage({ id: 'users.form.newpassword' })}
required
style={{ width: INPUT_WIDTH.default }}
></SealInput.Password>
</Form.Item>
<Form.Item
name="confirm_password"
dependencies={['new_password']}
rules={[
{
required: true,
message: intl.formatMessage({
id: 'users.password.confirm.empty'
})
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('new_password') === value) {
return Promise.resolve();
}
return Promise.reject(
new Error(
intl.formatMessage({ id: 'users.password.confirm.error' })
)
);
}
})
]}
>
<SealInput.Password
required={true}
style={{ width: INPUT_WIDTH.default }}
label={intl.formatMessage({ id: 'users.password.confirm' })}
/>
</Form.Item>
<FormButtons
htmlType="submit"
onCancel={handleCancel}
showCancel={false}
></FormButtons>
</Form>
</PageContainer>
);
};
export default Profile;
@@ -0,0 +1,30 @@
import useUserSettings from '@/hooks/use-user-settings';
import { Button, ColorPicker } from 'antd';
import React from 'react';
const CustomUI: React.FC = () => {
const { setUserSettings, userSettings } = useUserSettings();
const handleOnChange = (color: any, css: string) => {
const colorPrimary = color.toHexString();
if (!colorPrimary) return;
setUserSettings({
...userSettings,
colorPrimary: color.toHexString()
});
};
return (
<div>
<h1>Custom UI Component</h1>
<Button type="primary"></Button>
<div>
<ColorPicker onChange={handleOnChange} format="hex"></ColorPicker>
</div>
</div>
);
};
CustomUI.displayName = 'CustomUI';
export default CustomUI;
@@ -0,0 +1,125 @@
import FormButtons from '@/components/form-buttons';
import SealInput from '@/components/seal-form/seal-input';
import { PasswordReg } from '@/config';
import { INPUT_WIDTH } from '@/constants';
import { updatePassword } from '@/pages/login/apis';
import { useIntl } from '@umijs/max';
import { Form, message } from 'antd';
import React from 'react';
interface ProfileProps {
username?: string;
new_password: string;
current_password: string;
}
const Profile: React.FC = () => {
const [form] = Form.useForm();
const intl = useIntl();
const handleOnFinish = async (values: any) => {
try {
await updatePassword({
new_password: values.new_password,
current_password: values.current_password
});
message.success(intl.formatMessage({ id: 'common.message.success' }));
} catch (error) {}
};
const handleOnFinishFailed = (errorInfo: any) => {
console.log('handleOnFinishFailed', errorInfo);
};
const handleCancel = () => {
form.resetFields();
};
return (
<Form
style={{ width: '524px' }}
name="profileForm"
form={form}
onFinish={handleOnFinish}
onFinishFailed={handleOnFinishFailed}
>
<Form.Item<ProfileProps>
name="current_password"
rules={[
{
required: true,
message: intl.formatMessage(
{ id: 'common.form.rule.input' },
{
name: intl.formatMessage({
id: 'users.form.currentpassword'
})
}
)
}
]}
>
<SealInput.Password
label={intl.formatMessage({ id: 'users.form.currentpassword' })}
required
style={{ width: INPUT_WIDTH.default }}
></SealInput.Password>
</Form.Item>
<Form.Item<ProfileProps>
name="new_password"
rules={[
{
required: true,
pattern: PasswordReg,
message: intl.formatMessage({
id: 'users.form.rule.password'
})
}
]}
>
<SealInput.Password
label={intl.formatMessage({ id: 'users.form.newpassword' })}
required
style={{ width: INPUT_WIDTH.default }}
></SealInput.Password>
</Form.Item>
<Form.Item
name="confirm_password"
dependencies={['new_password']}
rules={[
{
required: true,
message: intl.formatMessage({
id: 'users.password.confirm.empty'
})
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('new_password') === value) {
return Promise.resolve();
}
return Promise.reject(
new Error(
intl.formatMessage({ id: 'users.password.confirm.error' })
)
);
}
})
]}
>
<SealInput.Password
required={true}
style={{ width: INPUT_WIDTH.default }}
label={intl.formatMessage({ id: 'users.password.confirm' })}
/>
</Form.Item>
<FormButtons
htmlType="submit"
onCancel={handleCancel}
showCancel={false}
></FormButtons>
</Form>
);
};
Profile.displayName = 'ModifyPassword';
export default Profile;
+55 -121
View File
@@ -1,134 +1,68 @@
import FormButtons from '@/components/form-buttons'; import useTabActive from '@/hooks/use-tab-active';
import SealInput from '@/components/seal-form/seal-input';
import { PasswordReg } from '@/config';
import { INPUT_WIDTH } from '@/constants';
import { updatePassword } from '@/pages/login/apis';
import { PageContainer } from '@ant-design/pro-components'; import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Form, message } from 'antd'; import { TabsProps } from 'antd';
import React, { useCallback, useState } from 'react';
import styled from 'styled-components';
import CustomUI from './components/custom-ui';
import ModifyPasswordn from './components/modify-password';
const Wrapper = styled.div`
.ant-page-header-heading {
padding-inline: 8px;
}
`;
interface ProfileProps {
username?: string;
new_password: string;
current_password: string;
}
const Profile: React.FC = () => { const Profile: React.FC = () => {
const [form] = Form.useForm();
const intl = useIntl(); const intl = useIntl();
const { setTabActive, getTabActive, tabsMap } = useTabActive();
const [activeKey, setActiveKey] = useState(
getTabActive(tabsMap.userSettings) || 'modify-password'
);
const handleOnFinish = async (values: any) => { const items: TabsProps['items'] = [
try { {
await updatePassword({ key: 'modify-password',
new_password: values.new_password, label: intl.formatMessage({ id: 'users.form.updatepassword' }),
current_password: values.current_password children: <ModifyPasswordn />
}); },
message.success(intl.formatMessage({ id: 'common.message.success' })); {
} catch (error) {} key: 'custom-ui',
}; label: 'Custom UI',
children: <CustomUI />
}
];
const handleOnFinishFailed = (errorInfo: any) => { const handleChangeTab = useCallback((key: string) => {
console.log('handleOnFinishFailed', errorInfo); setActiveKey(key);
}; setTabActive(tabsMap.userSettings, key);
const handleCancel = () => { }, []);
form.resetFields();
};
return ( return (
<PageContainer <Wrapper>
ghost <PageContainer
header={{ ghost
title: intl.formatMessage({ id: 'users.form.updatepassword' }), header={{
style: { title: 'User Settings',
paddingInline: 'var(--layout-content-header-inlinepadding)' style: {
} paddingInline: 'var(--layout-content-inlinepadding)'
}} }
extra={[]} }}
> tabList={items}
<Form onTabChange={handleChangeTab}
style={{ width: '524px' }} tabActiveKey={activeKey}
name="profileForm" tabProps={{
form={form} type: 'card',
onFinish={handleOnFinish} style: {
onFinishFailed={handleOnFinishFailed} marginTop: 78
> }
<Form.Item<ProfileProps> }}
name="current_password" extra={[]}
rules={[ ></PageContainer>
{ </Wrapper>
required: true,
message: intl.formatMessage(
{ id: 'common.form.rule.input' },
{
name: intl.formatMessage({
id: 'users.form.currentpassword'
})
}
)
}
]}
>
<SealInput.Password
label={intl.formatMessage({ id: 'users.form.currentpassword' })}
required
style={{ width: INPUT_WIDTH.default }}
></SealInput.Password>
</Form.Item>
<Form.Item<ProfileProps>
name="new_password"
rules={[
{
required: true,
pattern: PasswordReg,
message: intl.formatMessage({
id: 'users.form.rule.password'
})
}
]}
>
<SealInput.Password
label={intl.formatMessage({ id: 'users.form.newpassword' })}
required
style={{ width: INPUT_WIDTH.default }}
></SealInput.Password>
</Form.Item>
<Form.Item
name="confirm_password"
dependencies={['new_password']}
rules={[
{
required: true,
message: intl.formatMessage({
id: 'users.password.confirm.empty'
})
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('new_password') === value) {
return Promise.resolve();
}
return Promise.reject(
new Error(
intl.formatMessage({ id: 'users.password.confirm.error' })
)
);
}
})
]}
>
<SealInput.Password
required={true}
style={{ width: INPUT_WIDTH.default }}
label={intl.formatMessage({ id: 'users.password.confirm' })}
/>
</Form.Item>
<FormButtons
htmlType="submit"
onCancel={handleCancel}
showCancel={false}
></FormButtons>
</Form>
</PageContainer>
); );
}; };
Profile.displayName = 'Profile';
export default Profile; export default Profile;