You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
420 lines
13 KiB
TypeScript
420 lines
13 KiB
TypeScript
import './index.less'
|
|
|
|
import {
|
|
CloseOutlined,
|
|
CopyOutlined,
|
|
NotificationOutlined,
|
|
SettingOutlined
|
|
} from '@ant-design/icons-vue'
|
|
|
|
import {
|
|
Alert,
|
|
Button,
|
|
ConfigProvider,
|
|
Divider,
|
|
Drawer,
|
|
List,
|
|
message,
|
|
Switch
|
|
} from 'ant-design-vue'
|
|
import 'ant-design-vue/es/alert/style'
|
|
import 'ant-design-vue/es/button/style'
|
|
import 'ant-design-vue/es/divider/style'
|
|
import 'ant-design-vue/es/drawer/style'
|
|
import 'ant-design-vue/es/list/style'
|
|
import 'ant-design-vue/es/message/style'
|
|
import 'ant-design-vue/es/switch/style'
|
|
import 'ant-design-vue/es/tooltip/style'
|
|
import 'ant-design-vue/es/select/style'
|
|
|
|
import type { ProSettings } from '../../defaultSettings'
|
|
import { defaultSettings } from '../../defaultSettings'
|
|
|
|
import BlockCheckbox from './BlockCheckbox'
|
|
import ThemeColor from './ThemeColor'
|
|
import { gLocaleObject } from '../../locales'
|
|
import LayoutSetting, { renderLayoutSettingItem } from './LayoutChange'
|
|
import RegionalSetting from './RegionalChange'
|
|
import { genStringToTheme } from '../../utils/utils'
|
|
import type { VueNodeOrRender } from '#/types'
|
|
import type { PropType, FunctionalComponent } from 'vue'
|
|
|
|
type BodyProps = {
|
|
title: string
|
|
prefixCls: string
|
|
}
|
|
|
|
const Body: FunctionalComponent<BodyProps> = (props, { slots }) => (
|
|
<div style={{ marginBottom: '24px' }}>
|
|
<h3 class={`${props.prefixCls}-drawer-title`}>{props.title}</h3>
|
|
{slots.default?.()}
|
|
</div>
|
|
)
|
|
|
|
export type SettingItemProps = {
|
|
title: VueNodeOrRender
|
|
action: VueNodeOrRender
|
|
disabled?: boolean
|
|
disabledReason?: VueNodeOrRender
|
|
}
|
|
|
|
export type SettingDrawerProps = {
|
|
settings?: ProSettings
|
|
collapse?: boolean
|
|
getContainer?: any
|
|
hideHintAlert?: boolean
|
|
hideCopyButton?: boolean
|
|
/** 使用实验性质的黑色主题 */
|
|
enableDarkTheme?: boolean
|
|
prefixCls?: string
|
|
colorList?: false | { key: string; color: string }[]
|
|
onSettingChange?: (settings: ProSettings) => void
|
|
pathname?: string
|
|
disableUrlParams?: boolean
|
|
themeOnly?: boolean
|
|
}
|
|
|
|
const settingDrawerProps = {
|
|
defaultSettings: {
|
|
type: Object as PropType<ProSettings>,
|
|
default: () => defaultSettings
|
|
},
|
|
settings: {
|
|
type: Object as PropType<ProSettings>,
|
|
default: () => defaultSettings
|
|
},
|
|
collapse: Boolean,
|
|
getContainer: [Function, Object] as PropType<any>,
|
|
hideHintAlert: Boolean,
|
|
hideCopyButton: Boolean,
|
|
/** 使用实验性质的黑色主题 */
|
|
enableDarkTheme: Boolean,
|
|
prefixCls: {
|
|
type: String,
|
|
default: 'ant-pro'
|
|
},
|
|
colorList: {
|
|
type: [Boolean, Array] as PropType<SettingDrawerProps['colorList']>,
|
|
default: () => [
|
|
{ key: 'daybreak', color: '#1890ff' },
|
|
{ key: 'dust', color: '#F5222D' },
|
|
{ key: 'volcano', color: '#FA541C' },
|
|
{ key: 'sunset', color: '#FAAD14' },
|
|
{ key: 'cyan', color: '#13C2C2' },
|
|
{ key: 'green', color: '#52C41A' },
|
|
{ key: 'geekblue', color: '#2F54EB' },
|
|
{ key: 'purple', color: '#722ED1' }
|
|
]
|
|
},
|
|
onSettingChange: Function as PropType<SettingDrawerProps['onSettingChange']>,
|
|
pathname: { type: String, default: window.location.pathname },
|
|
disableUrlParams: { type: Boolean, default: true },
|
|
themeOnly: Boolean
|
|
}
|
|
|
|
type FormatMessageFunc = (data: { id: string; defaultMessage?: string }) => string
|
|
export const getFormatMessage = (): FormatMessageFunc => {
|
|
return ({ id }: { id: string; defaultMessage?: string }): string => {
|
|
const locales = gLocaleObject()
|
|
return locales[id]
|
|
}
|
|
}
|
|
|
|
const updateTheme = async (dark: boolean, color?: string) => {
|
|
if (typeof window === 'undefined') return
|
|
if (typeof window.MutationObserver === 'undefined') return
|
|
|
|
if (!ConfigProvider.config) return
|
|
ConfigProvider.config({
|
|
theme: {
|
|
primaryColor: genStringToTheme(color) || '#1890ff'
|
|
}
|
|
})
|
|
|
|
// if (dark) {
|
|
// const defaultTheme = {
|
|
// brightness: 100,
|
|
// contrast: 90,
|
|
// sepia: 10
|
|
// }
|
|
//
|
|
// const defaultFixes = {
|
|
// invert: [],
|
|
// css: '',
|
|
// ignoreInlineStyle: ['.react-switch-handle'],
|
|
// ignoreImageAnalysis: [],
|
|
// disableStyleSheetsProxy: true
|
|
// }
|
|
// if (window.MutationObserver && window.fetch) {
|
|
// setFetch(window.fetch)
|
|
// darkreaderEnable(defaultTheme, defaultFixes)
|
|
// }
|
|
// } else {
|
|
// if (window.MutationObserver) darkreaderDisable()
|
|
// }
|
|
}
|
|
|
|
const genCopySettingJson = (settingState: ProSettings) =>
|
|
JSON.stringify({ ...settingState }, null, 2)
|
|
|
|
/**
|
|
* 可视化配置组件
|
|
*
|
|
* @param props
|
|
*/
|
|
const SettingDrawer = defineComponent({
|
|
props: settingDrawerProps,
|
|
emits: ['update:collapse', 'update:settings'],
|
|
setup(props, { emit }) {
|
|
// const firstRender = ref<boolean>(true)
|
|
|
|
// 隐藏显示,支持 双向绑定
|
|
const show = ref<boolean>(false)
|
|
const setShow = (isShow: boolean) => {
|
|
show.value = isShow
|
|
emit('update:collapse', show.value)
|
|
}
|
|
watchEffect(() => {
|
|
show.value = props.collapse
|
|
})
|
|
|
|
const settingState = reactive<ProSettings>({})
|
|
watchEffect(() => {
|
|
Object.assign(settingState, props.settings)
|
|
})
|
|
|
|
// TODO 语言切换
|
|
|
|
// 监听更新主题色
|
|
const changeTheme = () =>
|
|
updateTheme(settingState.navTheme === 'realDark', settingState.primaryColor)
|
|
watch(() => settingState.primaryColor, changeTheme, { immediate: true })
|
|
watch(() => settingState.navTheme, changeTheme)
|
|
|
|
/**
|
|
* 修改设置
|
|
*
|
|
* @param key
|
|
* @param value
|
|
*/
|
|
const changeSetting = (key: string, value: string | boolean | number) => {
|
|
// @ts-ignore
|
|
settingState[key] = value
|
|
|
|
if (key === 'layout') {
|
|
settingState.contentWidth = value === 'top' ? 'Fixed' : 'Fluid'
|
|
}
|
|
if (key === 'layout' && value !== 'mix') {
|
|
settingState.splitMenus = false
|
|
}
|
|
if (key === 'layout' && value === 'mix') {
|
|
settingState.navTheme = 'light'
|
|
}
|
|
if (key === 'colorWeak' && value === true) {
|
|
const dom = document.querySelector('body')
|
|
if (dom) {
|
|
dom.dataset.prosettingdrawer = dom.style.filter
|
|
dom.style.filter = 'invert(80%)'
|
|
}
|
|
}
|
|
if (key === 'colorWeak' && value === false) {
|
|
const dom = document.querySelector('body')
|
|
if (dom) {
|
|
dom.style.filter = dom.dataset.prosettingdrawer || 'none'
|
|
delete dom.dataset.prosettingdrawer
|
|
}
|
|
}
|
|
|
|
emit('update:settings', toRaw(settingState))
|
|
}
|
|
|
|
const formatMessage = getFormatMessage()
|
|
|
|
return () => {
|
|
const baseClassName = `${props.prefixCls}-setting`
|
|
return (
|
|
<Drawer
|
|
visible={show.value}
|
|
width={300}
|
|
closable={false}
|
|
onClose={() => setShow(false)}
|
|
placement="right"
|
|
getContainer={props.getContainer}
|
|
handle={
|
|
<div class={`${baseClassName}-drawer-handle`} onClick={() => setShow(!show.value)}>
|
|
{show.value ? (
|
|
<CloseOutlined style={{ color: '#fff', fontSize: 20 }} />
|
|
) : (
|
|
<SettingOutlined style={{ color: '#fff', fontSize: 20 }} />
|
|
)}
|
|
</div>
|
|
}
|
|
style={{
|
|
zIndex: 999
|
|
}}
|
|
>
|
|
<div class={`${baseClassName}-drawer-content`}>
|
|
<Body
|
|
title={formatMessage({
|
|
id: 'app.setting.pagestyle',
|
|
defaultMessage: 'Page style setting'
|
|
})}
|
|
prefixCls={baseClassName}
|
|
>
|
|
<BlockCheckbox
|
|
prefixCls={baseClassName}
|
|
list={[
|
|
{
|
|
key: 'light',
|
|
title: formatMessage({
|
|
id: 'app.setting.pagestyle.light',
|
|
defaultMessage: '亮色菜单风格'
|
|
})
|
|
},
|
|
{
|
|
key: 'dark',
|
|
title: formatMessage({
|
|
id: 'app.setting.pagestyle.dark',
|
|
defaultMessage: '暗色菜单风格'
|
|
})
|
|
},
|
|
{
|
|
key: 'realDark',
|
|
title: formatMessage({
|
|
id: 'app.setting.pagestyle.realdark',
|
|
defaultMessage: '暗色菜单风格'
|
|
})
|
|
}
|
|
].filter(item => {
|
|
if (item.key === 'dark' && settingState.layout === 'mix') return false
|
|
return !(item.key === 'realDark' && !props.enableDarkTheme)
|
|
})}
|
|
value={settingState.navTheme!}
|
|
configType="theme"
|
|
key="navTheme"
|
|
onChange={value => changeSetting('navTheme', value)}
|
|
/>
|
|
</Body>
|
|
{props.colorList !== false && (
|
|
<Body
|
|
title={formatMessage({
|
|
id: 'app.setting.themecolor',
|
|
defaultMessage: 'Theme color'
|
|
})}
|
|
prefixCls={baseClassName}
|
|
>
|
|
<ThemeColor
|
|
colorList={props.colorList}
|
|
value={genStringToTheme(settingState.primaryColor)}
|
|
formatMessage={formatMessage}
|
|
onChange={color => changeSetting('primaryColor', color)}
|
|
/>
|
|
</Body>
|
|
)}
|
|
{!props.themeOnly && (
|
|
<>
|
|
<Divider />
|
|
<Body
|
|
prefixCls={baseClassName}
|
|
title={formatMessage({ id: 'app.setting.navigationmode' })}
|
|
>
|
|
<BlockCheckbox
|
|
prefixCls={baseClassName}
|
|
value={settingState.layout!}
|
|
key="layout"
|
|
configType="layout"
|
|
list={[
|
|
{
|
|
key: 'side',
|
|
title: formatMessage({ id: 'app.setting.sidemenu' })
|
|
},
|
|
{
|
|
key: 'top',
|
|
title: formatMessage({ id: 'app.setting.topmenu' })
|
|
},
|
|
{
|
|
key: 'mix',
|
|
title: formatMessage({ id: 'app.setting.mixmenu' })
|
|
}
|
|
]}
|
|
onChange={value => changeSetting('layout', value)}
|
|
/>
|
|
</Body>
|
|
<LayoutSetting settings={settingState} changeSetting={changeSetting} />
|
|
<Divider />
|
|
|
|
<Body
|
|
prefixCls={baseClassName}
|
|
title={formatMessage({ id: 'app.setting.regionalsettings' })}
|
|
>
|
|
<RegionalSetting settings={settingState} changeSetting={changeSetting} />
|
|
</Body>
|
|
|
|
<Divider />
|
|
|
|
<Body
|
|
prefixCls={baseClassName}
|
|
title={formatMessage({ id: 'app.setting.othersettings' })}
|
|
>
|
|
<List
|
|
split={false}
|
|
renderItem={renderLayoutSettingItem}
|
|
dataSource={[
|
|
{
|
|
title: formatMessage({ id: 'app.setting.weakmode' }),
|
|
action: (
|
|
<Switch
|
|
size="small"
|
|
class="color-weak"
|
|
v-model:checked={settingState.colorWeak}
|
|
onChange={checked => {
|
|
changeSetting('colorWeak', checked)
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
]}
|
|
/>
|
|
</Body>
|
|
{props.hideHintAlert && props.hideCopyButton ? null : <Divider />}
|
|
|
|
{props.hideHintAlert ? null : (
|
|
<Alert
|
|
type="warning"
|
|
message={formatMessage({
|
|
id: 'app.setting.production.hint'
|
|
})}
|
|
icon={<NotificationOutlined />}
|
|
showIcon
|
|
style={{ marginBottom: '16px' }}
|
|
/>
|
|
)}
|
|
|
|
{props.hideCopyButton ? null : (
|
|
<Button
|
|
block
|
|
icon={<CopyOutlined />}
|
|
style={{ marginBottom: '24px' }}
|
|
onClick={async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(genCopySettingJson(settingState))
|
|
message.success(formatMessage({ id: 'app.setting.copyinfo' }))
|
|
} catch (error) {
|
|
// console.log(error);
|
|
}
|
|
}}
|
|
>
|
|
{formatMessage({ id: 'app.setting.copy' })}
|
|
</Button>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</Drawer>
|
|
)
|
|
}
|
|
}
|
|
})
|
|
|
|
export default SettingDrawer
|