diff --git a/config-overrides.js b/config-overrides.js index 448e99c..1b479ef 100644 --- a/config-overrides.js +++ b/config-overrides.js @@ -2,6 +2,17 @@ const { override, addLessLoader, addWebpackAlias, fixBabelImports, addWebpackPlu const path = require('path') const darkThemeVars = require('antd/dist/dark-theme') const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const AntDesignThemePlugin = require('antd-theme-webpack-plugin'); + +const options = { + antDir: path.join(__dirname, './node_modules/antd'), + stylesDir: path.join(__dirname, './src/styles'), + varFile: path.join(__dirname, './src/styles/var.less'), + mainLessFile: path.join(__dirname, './src/styles/index.less'), + themeVariables: ['@primary-color'], + indexFileName: 'index.html', + generateOnce: false, +} const resolve = dir => path.join(__dirname, '.', dir) @@ -11,24 +22,25 @@ const rewiredSourceMap = () => config => { } module.exports = override( + fixBabelImports('import', { + libraryName: 'antd', + libraryDirectory: 'es', + style: true + }), addLessLoader({ - javascriptEnabled: true, modifyVars: { - hack: `true;@import "${require.resolve('antd/lib/style/color/colorPalette.less')}";`, - ...darkThemeVars, - '@primary-color': '#13c2c2', - '@dark-color': '#141414', - '@font-color': 'rgba(256, 256, 256, 0.85)' - } + // hack: `true;@import "${require.resolve('antd/lib/style/color/colorPalette.less')}";`, + // ...darkThemeVars, + // '@primary-color': '#13c2c2' + }, + javascriptEnabled: true, }), addWebpackAlias({ '~': resolve('src') }), - // addWebpackPlugin(new BundleAnalyzerPlugin()), - fixBabelImports('import', { - libraryName: 'antd', - libraryDirectory: 'es', - style: true - }), + addWebpackPlugin( + // new BundleAnalyzerPlugin(), + new AntDesignThemePlugin(options) + ), rewiredSourceMap() ) diff --git a/package.json b/package.json index 18b4249..5cbd1d0 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@types/recharts": "^1.8.5", "@types/styled-components": "^4.4.1", "@types/webpack-env": "^1.15.0", + "antd-theme-webpack-plugin": "^1.3.1", "babel-plugin-import": "^1.13.0", "babel-plugin-recharts": "^1.2.0", "customize-cra": "^0.9.1", @@ -61,7 +62,7 @@ "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.18.0", "http-proxy-middleware": "^0.20.0", - "less": "^3.10.3", + "less": "^2.7.0", "less-loader": "^5.0.0", "prettier": "^1.19.1", "react-app-rewired": "^2.1.5", diff --git a/src/actions/global.action.ts b/src/actions/global.action.ts index 6a31491..5f4e293 100644 --- a/src/actions/global.action.ts +++ b/src/actions/global.action.ts @@ -13,6 +13,9 @@ export interface GlobalState { /** user's language */ locale: 'zh_CN' | 'en_US' + + /** Is first time to view the site ? */ + newUser: boolean } const SETGLOBALITEM = 'SETGLOBALITEM' diff --git a/src/index.tsx b/src/index.tsx index ab0dbeb..29863f2 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,6 @@ import React from 'react' import ReactDOM from 'react-dom' -import './styles/index.less' +import './styles/main.less' import configureStore from './stores' import { Provider } from 'react-redux' import * as serviceWorker from './serviceWorker' diff --git a/src/locales/en-US/guide/index.ts b/src/locales/en-US/guide/index.ts index a0139c1..1ae8e51 100644 --- a/src/locales/en-US/guide/index.ts +++ b/src/locales/en-US/guide/index.ts @@ -19,5 +19,7 @@ export const enUS_guide = { 'app.guide.driverStep.pageTabs.title': 'Page Tabs', 'app.guide.driverStep.pageTabs.description': 'The history of the page you visited will be displayed here', 'app.guide.driverStep.pageTabsActions.title': 'Page Tabs Actions', - 'app.guide.driverStep.pageTabsActions.description': 'Click here to do some quick operations to the Page Tabs' + 'app.guide.driverStep.pageTabsActions.description': 'Click here to do some quick operations to the Page Tabs', + 'app.guide.driverStep.switchTheme.title': 'Switch Theme', + 'app.guide.driverStep.switchTheme.description': 'Click here to switch system theme color' } diff --git a/src/locales/en-US/user/avatorDropMenu.ts b/src/locales/en-US/user/avatorDropMenu.ts index d9601fa..8bd079e 100644 --- a/src/locales/en-US/user/avatorDropMenu.ts +++ b/src/locales/en-US/user/avatorDropMenu.ts @@ -1,4 +1,8 @@ export const enUS_avatorDropMenu = { 'header.avator.account': 'Account', - 'header.avator.logout': 'Logout' + 'header.avator.logout': 'Logout', + 'global.theme.switchTheme': 'Switch Theme', + 'global.theme.switchingTheme': 'Switching Theme...', + 'global.theme.switchThemeDone': 'Update theme successfully!', + 'global.theme.switchThemeFail': 'Update theme fail' } diff --git a/src/locales/zh-CN/guide/index.ts b/src/locales/zh-CN/guide/index.ts index b6459dc..c821290 100644 --- a/src/locales/zh-CN/guide/index.ts +++ b/src/locales/zh-CN/guide/index.ts @@ -14,5 +14,7 @@ export const zhCN_guide = { 'app.guide.driverStep.pageTabs.title': '页面标签', 'app.guide.driverStep.pageTabs.description': '你的浏览历史会在这里集中显示', 'app.guide.driverStep.pageTabsActions.title': '标签操作栏', - 'app.guide.driverStep.pageTabsActions.description': '点击这里可以对页面标签做一些快捷操作' + 'app.guide.driverStep.pageTabsActions.description': '点击这里可以对页面标签做一些快捷操作', + 'app.guide.driverStep.switchTheme.title': '切换主题', + 'app.guide.driverStep.switchTheme.description': '点击这里切换系统主题颜色' } diff --git a/src/locales/zh-CN/user/avatorDropMenu.ts b/src/locales/zh-CN/user/avatorDropMenu.ts index 6fd8119..ba501b9 100644 --- a/src/locales/zh-CN/user/avatorDropMenu.ts +++ b/src/locales/zh-CN/user/avatorDropMenu.ts @@ -1,4 +1,8 @@ export const zhCN_avatorDropMenu = { 'header.avator.account': '个人设置', - 'header.avator.logout': '退出登录' + 'header.avator.logout': '退出登录', + 'global.theme.switchTheme': '切换主题', + 'global.theme.switchingTheme': '切换主题中...', + 'global.theme.switchThemeDone': '主题更新成功', + 'global.theme.switchThemeFail': '主题更新失败' } diff --git a/src/pages/guide/index.less b/src/pages/guide/index.less index 7ebd8b4..6d75fae 100644 --- a/src/pages/guide/index.less +++ b/src/pages/guide/index.less @@ -1,3 +1,5 @@ +@import '~src/styles/var'; + .guide-page { .guide-intro { background-color: rgb(41, 42, 45); diff --git a/src/pages/guide/index.tsx b/src/pages/guide/index.tsx index b838731..356f8cd 100644 --- a/src/pages/guide/index.tsx +++ b/src/pages/guide/index.tsx @@ -1,77 +1,13 @@ -import React, { FC, useEffect, useState } from 'react' -import Driver from 'driver.js' +import React, { FC } from 'react' import 'driver.js/dist/driver.min.css' import { Button } from 'antd' import './index.less' import { useLocale } from '~/locales' +import useGuide from './useGuide' const GuidePage: FC = () => { - const [driver, setDriver] = useState() - const { locale, formatMessage } = useLocale() - - useEffect(() => { - const _driver = new Driver({ - opacity: 0.5, - className: 'driver-overlay', - closeBtnText: formatMessage({ id: 'app.guide.driverjs.closeBtnText' }), - prevBtnText: formatMessage({ id: 'app.guide.driverjs.prevBtnText' }), - nextBtnText: formatMessage({ id: 'app.guide.driverjs.nextBtnText' }), - doneBtnText: formatMessage({ id: 'app.guide.driverjs.doneBtnText' }) - }) - setDriver(_driver) - }, [locale, formatMessage]) - - const onDriverStart = () => { - setTimeout(() => { - driver.defineSteps([ - { - element: '#sidebar-trigger', - popover: { - title: formatMessage({ id: 'app.guide.driverStep.sidebarTrigger.title' }), - description: formatMessage({ id: 'app.guide.driverStep.sidebarTrigger.description' }), - position: 'bottom', - offset: 10 - } - }, - { - element: '#notice-center', - popover: { - title: formatMessage({ id: 'app.guide.driverStep.notices.title' }), - description: formatMessage({ id: 'app.guide.driverStep.notices.description' }), - position: 'bottom', - offset: -160 - } - }, - { - element: '#language-change', - popover: { - title: formatMessage({ id: 'app.guide.driverStep.switchLanguages.title' }), - description: formatMessage({ id: 'app.guide.driverStep.switchLanguages.description' }), - position: 'bottom', - offset: -170 - } - }, - { - element: '#pageTabs .ant-tabs-nav.ant-tabs-nav-animated', - popover: { - title: formatMessage({ id: 'app.guide.driverStep.pageTabs.title' }), - description: formatMessage({ id: 'app.guide.driverStep.pageTabs.description' }), - position: 'bottom', - offset: 30 - } - }, - { - element: '#pageTabs-actions svg', - popover: { - title: formatMessage({ id: 'app.guide.driverStep.pageTabsActions.title' }), - description: formatMessage({ id: 'app.guide.driverStep.pageTabsActions.description' }), - position: 'left' - } - } - ]) - driver.start() - }, 50) - } + const { formatMessage } = useLocale() + const { driverStart } = useGuide() return (
@@ -87,7 +23,7 @@ const GuidePage: FC = () => { .

-
diff --git a/src/pages/guide/useGuide.ts b/src/pages/guide/useGuide.ts new file mode 100644 index 0000000..1f571c3 --- /dev/null +++ b/src/pages/guide/useGuide.ts @@ -0,0 +1,94 @@ +import { useEffect, useRef } from 'react' +import Driver from 'driver.js' +import 'driver.js/dist/driver.min.css' +import './index.less' +import { useLocale } from '~/locales' + +export const useGuide = () => { + const { locale, formatMessage } = useLocale() + + const driver = useRef( + new Driver({ + keyboardControl: false, + allowClose: false, + overlayClickNext: true, + opacity: 0.5, + className: 'driver-overlay', + closeBtnText: formatMessage({ id: 'app.guide.driverjs.closeBtnText' }), + prevBtnText: formatMessage({ id: 'app.guide.driverjs.prevBtnText' }), + nextBtnText: formatMessage({ id: 'app.guide.driverjs.nextBtnText' }), + doneBtnText: formatMessage({ id: 'app.guide.driverjs.doneBtnText' }) + }) + ) + + useEffect(() => { + driver.current.defineSteps([ + { + element: '#sidebar-trigger', + popover: { + title: formatMessage({ id: 'app.guide.driverStep.sidebarTrigger.title' }), + description: formatMessage({ id: 'app.guide.driverStep.sidebarTrigger.description' }), + position: 'bottom', + offset: 10, + isFirst: true + } + }, + { + element: '#notice-center', + popover: { + title: formatMessage({ id: 'app.guide.driverStep.notices.title' }), + description: formatMessage({ id: 'app.guide.driverStep.notices.description' }), + position: 'bottom', + offset: -160 + } + }, + { + element: '#language-change', + popover: { + title: formatMessage({ id: 'app.guide.driverStep.switchLanguages.title' }), + description: formatMessage({ id: 'app.guide.driverStep.switchLanguages.description' }), + position: 'bottom', + offset: -170 + } + }, + { + element: '#pageTabs .ant-tabs-nav.ant-tabs-nav-animated', + popover: { + title: formatMessage({ id: 'app.guide.driverStep.pageTabs.title' }), + description: formatMessage({ id: 'app.guide.driverStep.pageTabs.description' }), + position: 'bottom', + offset: 30 + } + }, + { + element: '#pageTabs-actions svg', + popover: { + title: formatMessage({ id: 'app.guide.driverStep.pageTabsActions.title' }), + description: formatMessage({ id: 'app.guide.driverStep.pageTabsActions.description' }), + position: 'left' + } + }, + { + element: '#switchTheme', + popover: { + title: formatMessage({ id: 'app.guide.driverStep.switchTheme.title' }), + description: formatMessage({ id: 'app.guide.driverStep.switchTheme.description' }), + position: 'left', + isLast: true + } + } + ]) + }, [formatMessage, locale]) + + const driverStart = () => { + console.log('guide started') + localStorage.setItem('newUser', 'false') + driver.current.start() + } + + return { + driverStart + } +} + +export default useGuide diff --git a/src/pages/layout/index.less b/src/pages/layout/index.less index 1649bbd..7dc645c 100644 --- a/src/pages/layout/index.less +++ b/src/pages/layout/index.less @@ -1,3 +1,5 @@ +@import '~src/styles/var'; + .layout-page { &-header { padding: 0 15px; @@ -5,7 +7,7 @@ justify-content: space-between; align-items: center; box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.45); - background-color: @dark-color; + background-color: @dark-color !important; z-index: 9; } &-sider { @@ -118,3 +120,39 @@ line-height: 40px; text-align: center; } + +.themeSwitch { + position: fixed; + right: 32px; + bottom: 102px; + z-index: 2147483640; + cursor: pointer; + > span { + display: block; + text-align: center; + background: #fff; + border-radius: 4px; + width: 44px; + height: 44px; + line-height: 44px; + font-size: 22px; + } +} + +.theme-color-content { + display: flex; + .theme-color-block { + width: 20px; + height: 20px; + margin-right: 8px; + color: #fff; + font-weight: 700; + text-align: center; + border-radius: 2px; + cursor: pointer; + border-radius: 2px; + &:last-child { + margin-right: 0; + } + } +} diff --git a/src/pages/layout/index.tsx b/src/pages/layout/index.tsx index b8d88e2..29fc174 100644 --- a/src/pages/layout/index.tsx +++ b/src/pages/layout/index.tsx @@ -15,15 +15,18 @@ import SuspendFallbackLoading from './suspendFallbackLoading' import { getMenuList } from '~/api/layout.api' import { MenuList, MenuChild } from '~/interface/layout/menu.interface' import { setUserItem } from '~/actions/user.action' +import ThemeSwitch from './themeSwitch' +import { useGuide } from '../guide/useGuide' const { Sider, Content } = Layout const WIDTH = 992 const LayoutPage: FC = () => { const [menuList, setMenuList] = useState([]) - const { device, collapsed } = useSelector((state: AppState) => state.globalReducer) + const { device, collapsed, newUser } = useSelector((state: AppState) => state.globalReducer) const isMobile = device === 'MOBILE' const dispatch = useDispatch() + const { driverStart } = useGuide() const toggle = () => { dispatch( @@ -77,6 +80,11 @@ const LayoutPage: FC = () => { } }, [dispatch]) + useEffect(() => { + newUser && setTimeout(driverStart, 1000) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + return ( {!isMobile ? ( @@ -106,6 +114,7 @@ const LayoutPage: FC = () => { }> + diff --git a/src/pages/layout/themeSwitch.tsx b/src/pages/layout/themeSwitch.tsx new file mode 100644 index 0000000..454e5da --- /dev/null +++ b/src/pages/layout/themeSwitch.tsx @@ -0,0 +1,74 @@ +import React, { FC, useState } from 'react' +import { Popover, message } from 'antd' +import { CheckOutlined, FontColorsOutlined } from '@ant-design/icons' +import { useLocale } from '~/locales' + +const colors = [ + '#13C2C2', + 'rgb(24, 144, 255)', + 'rgb(245, 34, 45)', + 'rgb(250, 84, 28)', + 'rgb(250, 173, 20)', + 'rgb(82, 196, 26)', + 'rgb(47, 84, 235)', + 'rgb(114, 46, 209)' +] + +export const ThemeSwitch: FC = () => { + const [theme, setTheme] = useState(colors[0]) + const [vars, setVars] = useState(() => { + const data = Object.assign({}, JSON.parse(localStorage.getItem('app-theme')!)) + setTheme(data['@primary-color'] || colors[0]) + window.less.modifyVars(data) + return data + }) + const { formatMessage } = useLocale() + + const onThemeChange = (color: string) => { + message.loading(formatMessage({ id: 'global.theme.switchingTheme' })) + const newVar: any = { ...vars } + newVar['@primary-color'] = color + window.less + .modifyVars(newVar) + .then(() => { + setTheme(color) + setVars(newVar) + localStorage.setItem('app-theme', JSON.stringify(newVar)) + message.destroy() + message.success(formatMessage({ id: 'global.theme.switchThemeDone' })) + }) + .catch(() => { + message.error(formatMessage({ id: 'global.theme.switchThemeFail' })) + console.error('Failed to update theme') + }) + } + + return ( + + {colors.map(color => ( +
onThemeChange(color)} + > + {theme === color && } +
+ ))} + + } + > +
+ + + +
+
+ ) +} + +export default ThemeSwitch diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index c8f66c6..445d909 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -9,6 +9,9 @@ declare namespace NodeJS { declare global { interface Window { __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: Function + less: { + modifyVars: (arg: any) => Promise + } } } diff --git a/src/reducers/gloabal.reducer.ts b/src/reducers/gloabal.reducer.ts index b950d63..c566aa2 100644 --- a/src/reducers/gloabal.reducer.ts +++ b/src/reducers/gloabal.reducer.ts @@ -4,7 +4,8 @@ import { getGlobalState } from '~/utils/getGloabal' const globalState: GlobalState = { ...getGlobalState(), noticeCount: 0, - locale: (localStorage.getItem('locale')! || 'en_US') as any + locale: (localStorage.getItem('locale')! || 'en_US') as any, + newUser: JSON.parse(localStorage.getItem('newUser')!) ?? true } export const globalReducer = (state = globalState, actions: GlobalActions): GlobalState => { diff --git a/src/styles/index.less b/src/styles/index.less index e1b6107..ed7832e 100644 --- a/src/styles/index.less +++ b/src/styles/index.less @@ -1,3 +1,5 @@ @import './main.less'; @import './antd.reset.less'; + +@import './var.less'; diff --git a/src/styles/var.less b/src/styles/var.less index 8b13789..81d24de 100644 --- a/src/styles/var.less +++ b/src/styles/var.less @@ -1 +1,5 @@ +@import '../../node_modules/antd/lib/style/themes/dark.less'; +@primary-color: #13c2c2; +@dark-color: #141414; +@font-color: rgba(256, 256, 256, 0.85);