diff --git a/core/package.json b/core/package.json index 23800dfc..ace9daa6 100644 --- a/core/package.json +++ b/core/package.json @@ -12,14 +12,15 @@ "@turf/helpers": "^6.1.4", "@turf/transform-scale": "^7.2.0", "colorcolor": "^1.1.1", + "css-vars-ponyfill": "^2.2.1", "echarts": "^4.8.0", "lodash.clonedeep": "^4.5.0", "lodash.difference": "^4.5.0", "lodash.max": "^4.0.1", "lodash.merge": "^4.6.1", "lodash.mergewith": "^4.6.2", - "lodash.tonumber": "^4.0.3", "lodash.orderby": "^4.6.0", + "lodash.tonumber": "^4.0.3", "omit.js": "^2.0.2", "proj4": "^2.15.0", "simple-statistics": "^7.8.7", diff --git a/core/utils/global-event.ts b/core/utils/global-event.ts index 362d9a9d..663050ca 100644 --- a/core/utils/global-event.ts +++ b/core/utils/global-event.ts @@ -4,6 +4,8 @@ import { Events } from 'vue-iclient-core/types/event/Events'; export class GlobalEvent extends Events { _theme: any = themeFactory[1]; eventTypes: string[]; + on: (data: Record void>) => void; + un: (data: Record void>) => void; constructor() { super(); diff --git a/core/utils/style/color/__tests__/colorPalette.spec.js b/core/utils/style/color/__tests__/colorPalette.spec.js new file mode 100644 index 00000000..5a3d3fb0 --- /dev/null +++ b/core/utils/style/color/__tests__/colorPalette.spec.js @@ -0,0 +1,13 @@ +import colorPalette from '../colorPalette'; + +describe('colorPalette test', () => { + beforeAll(() => {}); + + it('colorPalette', done => { + const res = colorPalette('#fff', 2); + expect(res).toBe('#fff2f0'); + done(); + }); + + afterAll(() => {}); +}); diff --git a/core/utils/style/color/colorPalette.ts b/core/utils/style/color/colorPalette.ts new file mode 100644 index 00000000..be8bc873 --- /dev/null +++ b/core/utils/style/color/colorPalette.ts @@ -0,0 +1,63 @@ +import tinycolor from 'tinycolor2'; + +const hueStep = 2; +const saturationStep = 16; +const saturationStep2 = 5; +const brightnessStep1 = 5; +const brightnessStep2 = 15; +const lightColorCount = 5; +const darkColorCount = 4; + +function getHue(hsv: any, i: number, isLight: boolean): number { + let hue; + if (hsv.h >= 60 && hsv.h <= 240) { + hue = isLight ? hsv.h - hueStep * i : hsv.h + hueStep * i; + } else { + hue = isLight ? hsv.h + hueStep * i : hsv.h - hueStep * i; + } + if (hue < 0) { + hue += 360; + } else if (hue >= 360) { + hue -= 360; + } + return Math.round(hue); +} + +function getSaturation(hsv: any, i: number, isLight: boolean): number { + let saturation: number; + if (isLight) { + saturation = Math.round(hsv.s * 100) - saturationStep * i; + } else if (i === darkColorCount) { + saturation = Math.round(hsv.s * 100) + saturationStep; + } else { + saturation = Math.round(hsv.s * 100) + saturationStep2 * i; + } + if (saturation > 100) { + saturation = 100; + } + if (isLight && i === lightColorCount && saturation > 10) { + saturation = 10; + } + if (saturation < 6) { + saturation = 6; + } + return Math.round(saturation); +} + +function getValue(hsv: any, i: number, isLight: boolean): number { + if (isLight) { + return Math.round(hsv.v * 100) + brightnessStep1 * i; + } + return Math.round(hsv.v * 100) - brightnessStep2 * i; +} + +export default function colorPalette(color: string, index: number): string { + const isLight = index <= 6; + const hsv = tinycolor(color).toHsv(); + const i = isLight ? lightColorCount + 1 - index : index - lightColorCount - 1; + return tinycolor({ + h: getHue(hsv, i, isLight), + s: getSaturation(hsv, i, isLight), + v: getValue(hsv, i, isLight) + }).toHexString(false); +} diff --git a/core/utils/style/color/serialColors.ts b/core/utils/style/color/serialColors.ts new file mode 100644 index 00000000..42dafb3a --- /dev/null +++ b/core/utils/style/color/serialColors.ts @@ -0,0 +1,207 @@ +import colorPalette from 'vue-iclient-core/utils/style/color/colorPalette'; +import themeFactory from 'vue-iclient-core/utils/style/theme/theme.json'; +import { getColorWithOpacity, getDarkenColor, getDataType } from 'vue-iclient-core/utils/util'; +import cssVars from 'css-vars-ponyfill'; + +export interface styleConfigParams { + styleConfig: { + id?: string; + className?: string; + include?: string; + }; +} + +const lightTheme = themeFactory[1]; +export type ThemeStyleParams = typeof lightTheme & styleConfigParams; + +export interface FunctionColorParams { + successColor?: string | string[]; + infoColor?: string | string[]; + warningColor?: string | string[]; + dangerColor?: string | string[]; +} + +export interface ExtraColorParams { + [prop: string]: string; +} +export interface ColorGroupExtraColorParams { + colorGroup: string[]; + [prop: string]: any; +} + +export interface StyleReplacerParams { + themeStyle: ThemeStyleParams; + primarySerialColors?: string[]; + functionSerialColors?: FunctionColorParams; + extraSerialColors: ExtraColorParams; +} + +const $blue6 = '#1890ff'; +const $green6 = '#52c41a'; +const $glod6 = '#faad14'; +const $red6 = '#f5222d'; + +const antdPrimaryColor = $blue6; +const antdFunctionColors: FunctionColorParams = { + infoColor: $blue6, + successColor: $green6, + warningColor: $glod6, + dangerColor: $red6 +}; + +const isBrowser = typeof window !== 'undefined'; +const isNativeSupport = isBrowser && window.CSS && window.CSS.supports && window.CSS.supports('(--a: 0)'); + +export function getPrimarySerialColors(nextThemeInfo?: ThemeStyleParams | ColorGroupExtraColorParams): string[] { + const series = []; + const nextThemeStyle = nextThemeInfo; + let prevPrimaryColor: string; + if (nextThemeStyle && nextThemeStyle.colorGroup && nextThemeStyle.colorGroup[0]) { + prevPrimaryColor = nextThemeStyle.colorGroup[0]; + } + const acceptColor: string = prevPrimaryColor || antdPrimaryColor; + for (let index = 1; index <= 10; index++) { + let nextColor: string; + switch (index) { + case 2: + nextColor = nextThemeStyle.selectedColor || getColorWithOpacity(acceptColor, 0.15); + break; + case 5: + nextColor = nextThemeStyle.hoverColor || colorPalette(acceptColor, index); + break; + case 6: + nextColor = acceptColor; + break; + case 7: + nextColor = nextThemeStyle.clickColor || colorPalette(acceptColor, index); + break; + default: + nextColor = colorPalette(acceptColor, index); + break; + } + series.push(nextColor); + } + return series; +} + +export function getFunctionSerialColors(functionColors?: ThemeStyleParams): FunctionColorParams { + const seriesIndex = [1, 2, 3, 4, 5, 6, 7]; + const acceptFunctionColors = functionColors || antdFunctionColors; + const nextFunctionSerialColors: FunctionColorParams = {}; + for (const key in acceptFunctionColors) { + if (Object.prototype.hasOwnProperty.call(antdFunctionColors, key)) { + const color = acceptFunctionColors[key] || antdFunctionColors[key]; + nextFunctionSerialColors[key] = []; + seriesIndex.forEach(item => { + const nextColor = item === 6 ? color : colorPalette(color, item); + nextFunctionSerialColors[key].push(nextColor); + }); + } + } + return nextFunctionSerialColors; +} + +export function getExtralColors( + themeStyleData: ThemeStyleParams, + primarySerialColors: string[], + functionColors: FunctionColorParams +): ExtraColorParams { + const tableHeaderSortActiveBg = getDarkenColor(themeStyleData.backgroundLight, 3); + const extraSerialColors: ExtraColorParams = { + textColorWithoutOpacity: getColorWithOpacity(themeStyleData.textColor, 1, false), + backgroundWithoutOpacity: getColorWithOpacity(themeStyleData.background, 1, false), + componentBackgroundWithoutOpacity: getColorWithOpacity(themeStyleData.componentBackground, 1, false), + primaryShadowColor: getColorWithOpacity(primarySerialColors[4], 0.25), + dangerShadowColor: getColorWithOpacity(functionColors.dangerColor[4], 0.25), + disabledDarkenBgColor10: getDarkenColor(themeStyleData.containerDisabled, 10), + tableHeaderSortActiveBg, + tableHeaderFilterActiveBg: getDarkenColor(tableHeaderSortActiveBg, 5) + }; + return extraSerialColors; +} + +export function dealWithTheme(nextThemeStyle: ThemeStyleParams): StyleReplacerParams { + const defaultThemeStyle = nextThemeStyle.style || 'light'; + const defaultTheme = themeFactory.find((item: ThemeStyleParams) => item.label === defaultThemeStyle); + // 合并 lightTheme 是因为可能其他 theme 没有完整的参数,如 disableColor + const serialColorsReplacer = getPrimarySerialColors( + Object.assign({ colorGroup: defaultTheme && defaultTheme.colorGroup }, nextThemeStyle) + ); + const themeStyleData = Object.assign({}, lightTheme, defaultTheme, nextThemeStyle); + const functionSerialColorsReplacer = getFunctionSerialColors(themeStyleData); + const nextThemeStyleData = Object.assign({}, themeStyleData, { + selectedColor: serialColorsReplacer[1], + hoverColor: serialColorsReplacer[4], + clickColor: serialColorsReplacer[6] + }); + const nextThemeData = { + themeStyle: nextThemeStyleData, + primarySerialColors: serialColorsReplacer, + functionSerialColors: functionSerialColorsReplacer, + extraSerialColors: getExtralColors(themeStyleData, serialColorsReplacer, functionSerialColorsReplacer) + }; + setRootStyle(nextThemeData); + return nextThemeData; +} + +export function toStyleVariable(variable) { + return `--${variable.replace(/[A-Z]/g, '-$&').toLowerCase()}`; +} + +export function getRootStyleSelector(themeStyle: ThemeStyleParams) { + return themeStyle.styleConfig?.className && isNativeSupport ? `.${themeStyle.styleConfig.className}` : ':root'; +} + +function setRootStyle(themeData: StyleReplacerParams): void { + const { themeStyle, primarySerialColors, functionSerialColors, extraSerialColors } = themeData; + const primaryColor = themeStyle.colorGroup[0]; + const variables = { + '--antd-wave-shadow-color': primaryColor, + '--primary-color': primaryColor + }; + const themeInfo = Object.assign({}, themeStyle, extraSerialColors); + const themeKeys = Object.keys(themeInfo); + primarySerialColors.forEach((color: string, index: number) => { + const varKey = `--primary-${index + 1}`; + variables[varKey] = color; + }); + for (const key in functionSerialColors) { + functionSerialColors[key].forEach((color: string, index: number) => { + const varKey = `--${key.replace('Color', '')}-${index + 1}`; + variables[varKey] = color; + }); + } + themeKeys.forEach((key: string) => { + if (!['[object Object]', '[object Array]'].includes(getDataType(themeInfo[key]))) { + const varKey = toStyleVariable(key); + variables[varKey] = themeInfo[key] || 'rgba(0, 0, 0, 0)'; + } + }); + const rootStyleSelector = getRootStyleSelector(themeStyle); + const antdStyleId = themeStyle.styleConfig?.id || 'sm-component-style'; + const rootStyle = `${rootStyleSelector} ${JSON.stringify(variables, null, 2) + .replace(/(:.+),/g, '$1;') + .replace(/"/g, '')}`; + let antStyleTag = document.getElementById(antdStyleId); + if (!antStyleTag) { + antStyleTag = document.createElement('style'); + antStyleTag.setAttribute('id', antdStyleId); + antStyleTag.setAttribute('type', 'text/css'); + document.head.insertBefore(antStyleTag, document.head.firstChild); + } + const defaultInclude = 'style#sm-component-style, link[href*=vue-iclient-mapboxgl]'; + const options = { + include: themeStyle.styleConfig?.include || defaultInclude, + silent: true, + onlyLegacy: true, + variables: {}, + watch: false + }; + if (!isNativeSupport) { + options.onlyLegacy = false; + options.watch = true; + options.variables = variables; + } + cssVars(options); + antStyleTag.innerHTML = rootStyle; +} diff --git a/core/utils/style/theme/set-theme.ts b/core/utils/style/theme/set-theme.ts new file mode 100644 index 00000000..c96691cd --- /dev/null +++ b/core/utils/style/theme/set-theme.ts @@ -0,0 +1,34 @@ +import globalEvent from 'vue-iclient-core/utils/global-event'; +import themeFactory from 'vue-iclient-core/utils/style/theme/theme.json'; +import { dealWithTheme, ThemeStyleParams } from 'vue-iclient-core/utils/style/color/serialColors'; +import { objectWithoutProperties } from 'vue-iclient-core/utils/util'; + +interface triggerParams { + triggerEvent: boolean; + ignoreElements: string[]; +} + +export const setTheme = (themeStyle: any = {}, triggerInfo?: triggerParams) => { + let acceptedThemeStyle = themeStyle; + if (typeof themeStyle === 'string') { + acceptedThemeStyle = themeFactory.find((item: ThemeStyleParams) => item.label === themeStyle) || themeFactory[1]; + } else if (acceptedThemeStyle.componentBackground) { + if (!acceptedThemeStyle.collapseCardHeaderBg) { + acceptedThemeStyle.collapseCardHeaderBg = acceptedThemeStyle.componentBackground; + } + if (!acceptedThemeStyle.collapseCardBackground) { + acceptedThemeStyle.collapseCardBackground = acceptedThemeStyle.componentBackground; + } + } + const nextThemeData = dealWithTheme(acceptedThemeStyle); + const nextTheme = { + ...nextThemeData.themeStyle + }; + if (themeStyle && (typeof themeStyle === 'string' || 'componentBackground' in themeStyle)) { + nextTheme.background = nextTheme.componentBackground; + } + globalEvent.theme = nextTheme; + if (!triggerInfo || triggerInfo.triggerEvent === true) { + globalEvent.changeTheme(objectWithoutProperties(nextTheme, (triggerInfo || {}).ignoreElements || [])); + } +}; diff --git a/vue3/package.json b/vue3/package.json index 9161b353..39690f0e 100644 --- a/vue3/package.json +++ b/vue3/package.json @@ -38,6 +38,7 @@ "@types/mapbox-gl": "^3.4.1", "ant-design-vue": "^4.2.6", "colorcolor": "^1.1.1", + "css-vars-ponyfill": "^2.2.1", "echarts": "^4.8.0", "lodash-es": "^4.17.21", "lodash.clonedeep": "^4.5.0", diff --git a/vue3/packages/common/components/collapse-card/collapseCard.vue b/vue3/packages/common/components/collapse-card/collapseCard.vue index 58184d30..fd0f9550 100644 --- a/vue3/packages/common/components/collapse-card/collapseCard.vue +++ b/vue3/packages/common/components/collapse-card/collapseCard.vue @@ -12,8 +12,7 @@ :style="[collapseCardHeaderBgStyle, headingTextColorStyle]" @click="iconClicked" > - - +
diff --git a/vue3/packages/mapboxgl/components/layer-list/layerGroup.vue b/vue3/packages/mapboxgl/components/layer-list/layerGroup.vue index 94bf3a9a..27dd2866 100644 --- a/vue3/packages/mapboxgl/components/layer-list/layerGroup.vue +++ b/vue3/packages/mapboxgl/components/layer-list/layerGroup.vue @@ -13,18 +13,18 @@ {{ item.title }}
-
-
-
- - - + />
@@ -58,7 +54,6 @@ diff --git a/vue3/packages/mapboxgl/theme-chalk/base.scss b/vue3/packages/mapboxgl/theme-chalk/base.scss index f934766c..51fd6e65 100644 --- a/vue3/packages/mapboxgl/theme-chalk/base.scss +++ b/vue3/packages/mapboxgl/theme-chalk/base.scss @@ -1,5 +1,3 @@ -// @use '../../common/theme-chalk/base/theme.scss'; -// @use '../../../../core/assets/iconfont/icon-sm-components.css'; @use '@supermapgis/common/theme-chalk/mixins/mixins' as * ; @use '@supermapgis/common/theme-chalk/base/theme.scss'; @use 'vue-iclient-core/assets/iconfont/icon-sm-components.css'; \ No newline at end of file diff --git a/vue3/packages/mapboxgl/theme-chalk/layerList.scss b/vue3/packages/mapboxgl/theme-chalk/layerList.scss index 0e572fda..4e932856 100644 --- a/vue3/packages/mapboxgl/theme-chalk/layerList.scss +++ b/vue3/packages/mapboxgl/theme-chalk/layerList.scss @@ -1,2 +1,2 @@ -@use '../../common/theme-chalk/mixins/mixins.scss' as *; -@use '../../common/theme-chalk/base/theme.scss' as *; +@use './base.scss'; +@use '@supermapgis/common/theme-chalk/collapseCard.scss' diff --git a/vue3/site/src/App.vue b/vue3/site/src/App.vue index 580072d5..1b07d19f 100644 --- a/vue3/site/src/App.vue +++ b/vue3/site/src/App.vue @@ -1,12 +1,56 @@ - + diff --git a/vue3/site/vite.config.js b/vue3/site/vite.config.js index c0074e78..6e97e15e 100644 --- a/vue3/site/vite.config.js +++ b/vue3/site/vite.config.js @@ -12,18 +12,21 @@ export default defineConfig({ plugins: [vue(), vueDevTools()], resolve: { alias: { - '@supermapgis/mapboxgl': fileURLToPath( - new URL('../packages/mapboxgl', import.meta.url) - ), - '@supermapgis/common': fileURLToPath( - new URL('../packages/common', import.meta.url) - ), - 'vue-iclient-core': fileURLToPath( - new URL('../../core', import.meta.url) - ) + '@supermapgis/mapboxgl': fileURLToPath(new URL('../packages/mapboxgl', import.meta.url)), + '@supermapgis/common': fileURLToPath(new URL('../packages/common', import.meta.url)), + 'vue-iclient-core': fileURLToPath(new URL('../../core', import.meta.url)) // node_modules: fileURLToPath(new URL('../node_modules', import.meta.url)) } }, + server: { + fs: { + allow: [ + // 添加包含字体文件的目录 + fileURLToPath(new URL('../../core/assets/iconfont', import.meta.url)), + fileURLToPath(new URL('../', import.meta.url)), + ] + } + }, css: { preprocessorOptions: { scss: {