diff --git a/CHANGELOG.md b/CHANGELOG.md index 52420f12d7142d77030340584c01d0f93ed5b239..0030a0dfaf14d0b2f5c91188c9267c6666a22504 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ### Added - 新增重复器表格组件 - +- 新增主题切换组件(直接内容项扩展,预置类型为THEME_TOGGLING) ### Change - 数据看板的直接内容不显示标题栏 diff --git a/src/locale/en/index.ts b/src/locale/en/index.ts index 302a7f788401d7a17c924f570241e636c1a5fb86..af264caaed5ae7fda5d6a344f794e1cbbd3b7398 100644 --- a/src/locale/en/index.ts +++ b/src/locale/en/index.ts @@ -320,6 +320,11 @@ export default { onlyShowUnread: 'Only show unread', }, }, + themeToggling: { + auto: 'Follow system', + light: 'Light', + dark: 'Dark', + } }, // 工具 util: { diff --git a/src/locale/zh-CN/index.ts b/src/locale/zh-CN/index.ts index cd53e400a0f33dfdeeb4ff410d88742716f26dca..dcad276a9c19d14133c3f584d6f3a51888aaa2d1 100644 --- a/src/locale/zh-CN/index.ts +++ b/src/locale/zh-CN/index.ts @@ -307,6 +307,11 @@ export default { onlyShowUnread: '只显示未读', }, }, + themeToggling: { + auto: '跟随系统', + light: '亮色主题', + dark: '暗色主题' + } }, // 工具 util: { diff --git a/src/panel-component/index.ts b/src/panel-component/index.ts index 5b7163aaa8dacd7aefb4b8faf2704a77eefcd5b4..a0659a9eca43d3e24bde1ebc2f06f1e0014c2623 100644 --- a/src/panel-component/index.ts +++ b/src/panel-component/index.ts @@ -39,9 +39,11 @@ import IBizViewMessage from './view-message'; import IBizViewMsgPos from './view-msg-pos'; import IBizSettingContainer from './setting-container'; import IBizSplitContainer from './split-container'; +import IBizThemeToggling from './theme-toggling'; export const IBizPanelComponents = { install: (v: App): void => { + v.use(IBizThemeToggling); v.use(IBizSplitContainer); v.use(IBizPanelCtrlViewPageCaption); v.use(IBizPanelContainer); diff --git a/src/panel-component/theme-toggling/index.ts b/src/panel-component/theme-toggling/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..7bc29aef1e4886b4197927e5605aa7a38a2f4958 --- /dev/null +++ b/src/panel-component/theme-toggling/index.ts @@ -0,0 +1,15 @@ +import { App } from 'vue'; +import { registerPanelItemProvider } from '@ibiz-template/runtime'; +import { withInstall } from '@ibiz-template/vue3-util'; +import { ThemeToggling } from './theme-toggling'; +import { ThemeTogglingProvider } from './theme-toggling.provider'; + +export const IBizThemeToggling = withInstall(ThemeToggling, function (v: App) { + v.component(ThemeToggling.name!, ThemeToggling); + registerPanelItemProvider( + 'RAWITEM_THEME_TOGGLING', + () => new ThemeTogglingProvider(), + ); +}); + +export default IBizThemeToggling; \ No newline at end of file diff --git a/src/panel-component/theme-toggling/theme-toggling.controller.ts b/src/panel-component/theme-toggling/theme-toggling.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..0c892fe8a5196b9f72f7a2c27f248c1ddf85d233 --- /dev/null +++ b/src/panel-component/theme-toggling/theme-toggling.controller.ts @@ -0,0 +1,96 @@ +import { PanelItemController } from '@ibiz-template/runtime'; +import { IPanelRawItem } from '@ibiz/model-core'; +import { ThemeTogglingState } from './theme-toggling.state'; + +/** + * @description 主题切换控制器 + * @export + * @class ThemeTogglingController + * @extends {PanelItemController} + */ +export class ThemeTogglingController extends PanelItemController { + /** + * @description 状态 + * @type {ThemeTogglingState} + * @memberof ThemeTogglingController + */ + declare state: ThemeTogglingState; + + /** + * @description 媒体查询 + * @protected + * @type {MediaQueryList} + * @memberof ThemeTogglingController + */ + protected mediaQuery!: MediaQueryList; + + /** + * @description 创建状态对象 + * @protected + * @returns {*} {ThemeTogglingState} + * @memberof ThemeTogglingController + */ + protected createState(): ThemeTogglingState { + return new ThemeTogglingState(this.parent?.state); + } + + /** + * @description 初始化 + * @protected + * @returns {*} {Promise} + * @memberof ThemeTogglingController + */ + protected async onInit(): Promise { + await super.onInit(); + this.state.theme = ibiz.util.theme.getTheme(); + this.handleSystemThemeChange = this.handleSystemThemeChange.bind(this); + this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + this.mediaQuery.addEventListener('change', this.handleSystemThemeChange); + } + + /** + * @description 获取系统主题 + * @protected + * @returns {*} {('light' | 'dark')} + * @memberof ThemeTogglingController + */ + protected getSystemTheme(): 'light' | 'dark' { + // 检测系统当前是否使用深色主题 + const isDarkMode = this.mediaQuery.matches; + if (isDarkMode) return 'dark'; + // 系统没有明确偏好 默认为亮色 + return 'light'; + } + + /** + * @description 处理系统主题变更 + * @protected + * @param {MediaQueryListEvent} ev + * @memberof ThemeTogglingController + */ + protected handleSystemThemeChange(ev: MediaQueryListEvent): void { + const themeName = ev.matches ? 'dark' : 'light'; + if (this.state.theme === 'auto') ibiz.util.theme.setTheme(themeName); + } + + /** + * @description 切换主题 + * @param {string} theme + * @memberof ThemeTogglingController + */ + switchTheme(theme: string): void { + if (theme === this.state.theme) return; + this.state.theme = theme; + const themeName = theme === 'auto' ? this.getSystemTheme() : theme; + ibiz.util.theme.setTheme(themeName); + } + + /** + * @description 销毁 + * @memberof ThemeTogglingController + */ + destroy(): void { + super.destroy(); + this.mediaQuery.removeEventListener('change', this.handleSystemThemeChange); + } +} diff --git a/src/panel-component/theme-toggling/theme-toggling.provider.ts b/src/panel-component/theme-toggling/theme-toggling.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..aa9182700031d31768599c7054c125460baba4e6 --- /dev/null +++ b/src/panel-component/theme-toggling/theme-toggling.provider.ts @@ -0,0 +1,27 @@ +import { + PanelController, + IPanelItemProvider, + PanelItemController, +} from '@ibiz-template/runtime'; +import { IPanelItem } from '@ibiz/model-core'; +import { ThemeTogglingController } from './theme-toggling.controller'; + +/** + * @description 主题切换适配器 + * @export + * @class ThemeTogglingProvider + * @implements {IPanelItemProvider} + */ +export class ThemeTogglingProvider implements IPanelItemProvider { + component: string = 'IBizThemeToggling'; + + async createController( + panelItem: IPanelItem, + panel: PanelController, + parent: PanelItemController | undefined, + ): Promise { + const c = new ThemeTogglingController(panelItem, panel, parent); + await c.init(); + return c; + } +} diff --git a/src/panel-component/theme-toggling/theme-toggling.scss b/src/panel-component/theme-toggling/theme-toggling.scss new file mode 100644 index 0000000000000000000000000000000000000000..43dba44269ccb778b6fa874c9b63cef57b5252a3 --- /dev/null +++ b/src/panel-component/theme-toggling/theme-toggling.scss @@ -0,0 +1,13 @@ +@include b(theme-toggling) { + @include e(item) { + &.van-cell { + padding: getCssVar(spacing, tight) getCssVar(spacing, base); + font-size: getCssVar(font-size, regular); + color: getCssVar(color, text-0); + background-color: getCssVar(color, bg-2); + } + } + @include e(icon) { + color: getCssVar(color, primary, active); + } +} \ No newline at end of file diff --git a/src/panel-component/theme-toggling/theme-toggling.state.ts b/src/panel-component/theme-toggling/theme-toggling.state.ts new file mode 100644 index 0000000000000000000000000000000000000000..ba3b5bfcb795fd4012d8746b96e097826e037295 --- /dev/null +++ b/src/panel-component/theme-toggling/theme-toggling.state.ts @@ -0,0 +1,16 @@ +import { PanelItemState } from '@ibiz-template/runtime'; + +/** + * @description 主题切换状态 + * @export + * @class ThemeTogglingState + * @extends {PanelItemState} + */ +export class ThemeTogglingState extends PanelItemState { + /** + * @description 主题 + * @type {('light' | 'dark' | 'auto' | string)} + * @memberof ThemeTogglingState + */ + theme: 'light' | 'dark' | 'auto' | string = ''; +} diff --git a/src/panel-component/theme-toggling/theme-toggling.tsx b/src/panel-component/theme-toggling/theme-toggling.tsx new file mode 100644 index 0000000000000000000000000000000000000000..216663c7f54d5be5b23c12fc408c9c5150bdc137 --- /dev/null +++ b/src/panel-component/theme-toggling/theme-toggling.tsx @@ -0,0 +1,78 @@ +import { PropType, defineComponent } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { IPanelRawItem } from '@ibiz/model-core'; +import { ThemeTogglingController } from './theme-toggling.controller'; +import './theme-toggling.scss'; + +export const ThemeToggling = defineComponent({ + name: 'IBizThemeToggling', + props: { + /** + * @description 首页导航占位模型数据 + */ + modelData: { + type: Object as PropType, + required: true, + }, + /** + * @description 首页导航占位控制器 + */ + controller: { + type: Object as PropType, + required: true, + }, + }, + setup(props) { + const c = props.controller; + const ns = useNamespace('theme-toggling'); + + const themeList = [ + { + title: ibiz.i18n.t('panelComponent.themeToggling.auto'), + value: 'auto', + }, + { + title: ibiz.i18n.t('panelComponent.themeToggling.light'), + value: 'light', + }, + { + title: ibiz.i18n.t('panelComponent.themeToggling.dark'), + value: 'dark', + }, + ]; + + return { ns, c, themeList }; + }, + render() { + return ( + + {this.themeList.map(theme => { + return ( + this.c.switchTheme(theme.value)} + > + {{ + 'right-icon': () => { + return this.c.state.theme === theme.value ? ( + + ) : null; + }, + }} + + ); + })} + + ); + }, +});