Merge pull request #708 from Tencent/develop

This commit is contained in:
悠静萝莉 2024-04-02 23:26:52 +08:00 committed by GitHub
commit ce285fa443
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 225 additions and 275 deletions

View File

@ -1,4 +1,4 @@
module.exports = {
export default {
// 一行最多 120 字符..
printWidth: 120,
// 使用 2 个空格缩进

View File

@ -1,5 +1,5 @@
// commit-lint config
module.exports = {
export default {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [

View File

@ -1,12 +1,14 @@
{
"name": "@tencent/tdesign-vue-next-starter",
"version": "0.9.0",
"version": "0.10.0",
"type": "module",
"scripts": {
"dev:mock": "vite --open --mode mock",
"dev": "vite --open --mode development",
"dev:linux": "vite --mode development",
"build:test": "vite build --mode test",
"build": "vue-tsc --noEmit && vite build --mode release",
"build:type": "vue-tsc --noEmit",
"build:site": "vue-tsc --noEmit && vite build --mode site",
"preview": "vite preview",
"lint": "eslint --ext .vue,.js,.jsx,.ts,.tsx ./ --max-warnings 0",
@ -19,10 +21,10 @@
"test:coverage": "echo \"no test:coverage specified,work in process\""
},
"dependencies": {
"@vueuse/core": "^10.6.1",
"axios": "^1.6.2",
"@vueuse/core": "^10.7.2",
"axios": "^1.6.7",
"dayjs": "^1.11.10",
"echarts": "5.1.2",
"echarts": "5.4.3",
"lodash": "^4.17.21",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
@ -30,50 +32,50 @@
"qrcode.vue": "^3.4.1",
"qs": "^6.11.2",
"tdesign-icons-vue-next": "^0.2.2",
"tdesign-vue-next": "^1.6.8",
"tdesign-vue-next": "^1.9.3",
"tvision-color": "^1.6.0",
"vue": "~3.3.8",
"vue-i18n": "^9.6.5",
"vue-router": "~4.2.4"
"vue": "~3.4.21",
"vue-i18n": "^9.9.1",
"vue-router": "~4.3.0"
},
"devDependencies": {
"@commitlint/cli": "^18.4.1",
"@commitlint/config-conventional": "^18.4.0",
"@commitlint/cli": "^18.6.0",
"@commitlint/config-conventional": "^18.6.0",
"@types/echarts": "^4.9.21",
"@types/lodash": "^4.14.201",
"@types/nprogress": "^0.2.3",
"@types/qs": "^6.9.10",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"@vitejs/plugin-vue": "^4.4.1",
"@types/qs": "^6.9.11",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.0.2",
"@vue/compiler-sfc": "^3.3.8",
"@vue/compiler-sfc": "~3.3.8",
"@vue/eslint-config-typescript": "^12.0.0",
"commitizen": "^4.3.0",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.53.0",
"eslint": "^8.56.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-vue": "^9.18.1",
"eslint-plugin-vue-scoped-css": "^2.5.1",
"husky": "^8.0.3",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-vue": "^9.21.1",
"eslint-plugin-vue-scoped-css": "^2.7.2",
"husky": "^9.0.10",
"less": "^4.2.0",
"lint-staged": "^15.1.0",
"lint-staged": "^15.2.2",
"mockjs": "^1.1.0",
"postcss-html": "^1.5.0",
"postcss-html": "^1.6.0",
"postcss-less": "^6.0.0",
"prettier": "^3.1.0",
"stylelint": "~15.11.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-order": "~6.0.3",
"typescript": "~5.3.2",
"vite": "^4.5.0",
"vite-plugin-mock": "^3.0.0",
"vite-svg-loader": "^4.0.0",
"vue-tsc": "^1.8.22"
"prettier": "^3.2.5",
"stylelint": "~16.2.1",
"stylelint-config-standard": "^36.0.0",
"stylelint-order": "~6.0.4",
"typescript": "~5.4.3",
"vite": "^5.1.0",
"vite-plugin-mock": "^3.0.1",
"vite-svg-loader": "^5.1.0",
"vue-tsc": "^1.8.27"
},
"config": {
"commitizen": {
@ -90,6 +92,6 @@
]
},
"engines": {
"node": ">=16.0.0"
"node": ">=18.0.0"
}
}

View File

@ -1,7 +1,7 @@
<template>
<t-config-provider :global-config="getComponentsLocale">
<router-view :key="locale" :class="[mode]"
/></t-config-provider>
<router-view :key="locale" :class="[mode]" />
</t-config-provider>
</template>
<script setup lang="ts">
import { computed } from 'vue';

View File

@ -5,6 +5,7 @@ export default {
mode: 'light',
layout: 'side',
splitMenu: false,
sideMode: 'light',
isFooterAside: false,
isSidebarFixed: true,
isHeaderFixed: true,

View File

@ -1,34 +0,0 @@
import debounce from 'lodash/debounce';
import { onMounted, onUnmounted } from 'vue';
interface WindowSizeOptions {
immediate?: boolean;
}
interface Fn<T = any, R = T> {
(...arg: T[]): R;
}
export function useWindowSizeFn<T>(fn: Fn<T>, options?: WindowSizeOptions, wait = 150) {
const handleSize: () => void = debounce(fn, wait);
const start = () => {
if (options && options.immediate) {
fn();
}
window.addEventListener('resize', handleSize);
};
const stop = () => {
window.removeEventListener('resize', handleSize);
};
onMounted(() => {
start();
});
onUnmounted(() => {
stop();
});
return [start, stop];
}

View File

@ -1,6 +1,6 @@
<template>
<router-view v-if="!isRefreshing" v-slot="{ Component }">
<transition name="fade">
<transition name="fade" mode="out-in">
<keep-alive :include="aliveViews">
<component :is="Component" />
</keep-alive>
@ -54,7 +54,7 @@ const isRefreshing = computed(() => {
transition: opacity @anim-duration-slow @anim-time-fn-easing;
}
.fade-enter,
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}

View File

@ -6,17 +6,19 @@
</div>
</template>
<script lang="ts" setup>
import { useWindowSize } from '@vueuse/core';
import debounce from 'lodash/debounce';
import { computed, CSSProperties, ref, unref, watch } from 'vue';
import { prefix } from '@/config/global';
import { useWindowSizeFn } from '@/hooks/event/useWindowSizeFn';
import { useSettingStore } from '@/store';
defineProps({
frameSrc: String,
});
const { width, height } = useWindowSize();
const loading = ref(true);
const heightRef = ref(window.innerHeight);
const frameRef = ref<HTMLFrameElement>();
@ -69,8 +71,8 @@ function hideLoading() {
calcHeight();
}
useWindowSizeFn(calcHeight, { immediate: true });
//
watch([width, height], debounce(calcHeight, 250));
watch(
[() => settingStore.showFooter, () => settingStore.isUseTabsRouter, () => settingStore.showBreadcrumb],
debounce(calcHeight, 250),

View File

@ -38,9 +38,13 @@
<translate-icon />
</t-button>
<t-dropdown-menu>
<t-dropdown-item v-for="(lang, index) in langList" :key="index" :value="lang.value" @click="changeLang">{{
lang.content
}}</t-dropdown-item></t-dropdown-menu
<t-dropdown-item
v-for="(lang, index) in langList"
:key="index"
:value="lang.value"
@click="(options) => changeLang(options.value as string)"
>{{ lang.content }}</t-dropdown-item
></t-dropdown-menu
>
</t-dropdown>
<t-dropdown :min-column-width="120" trigger="click">
@ -85,7 +89,7 @@ import { langList } from '@/locales/index';
import { useLocale } from '@/locales/useLocale';
import { getActive } from '@/router';
import { useSettingStore, useUserStore } from '@/store';
import type { MenuRoute } from '@/types/interface';
import type { MenuRoute, ModeType } from '@/types/interface';
import MenuContent from './MenuContent.vue';
import Notice from './Notice.vue';
@ -147,11 +151,11 @@ const menuCls = computed(() => {
},
];
});
const menuTheme = computed(() => props.theme as 'light' | 'dark');
const menuTheme = computed(() => props.theme as ModeType);
//
const { changeLocale } = useLocale();
const changeLang = ({ value: lang }: { value: string }) => {
const changeLang = (lang: string) => {
changeLocale(lang);
};
@ -238,7 +242,6 @@ const navToHelper = () => {
display: flex;
align-items: normal;
line-height: 0;
padding-left: var(--td-comp-margin-xl);
}
.header-logo-container {

View File

@ -7,7 +7,7 @@
:class="`${prefix}-layout-tabs-nav`"
:value="$route.path"
:style="{ position: 'sticky', top: 0, width: '100%' }"
@change="handleChangeCurrentTab"
@change="(value) => handleChangeCurrentTab(value as string)"
@remove="handleRemove"
@drag-sort="handleDragend"
>

View File

@ -5,7 +5,7 @@
:layout="settingStore.layout"
:is-fixed="settingStore.isSidebarFixed"
:menu="sideMenu"
:theme="settingStore.displayMode"
:theme="settingStore.displaySideMode"
:is-compact="settingStore.isSidebarCompact"
/>
</template>

View File

@ -3,12 +3,12 @@
<t-menu :class="menuCls" :theme="theme" :value="active" :collapsed="collapsed" :default-expanded="defaultExpanded">
<template #logo>
<span v-if="showLogo" :class="`${prefix}-side-nav-logo-wrapper`" @click="goHome">
<component :is="getLogo()" :class="`${prefix}-side-nav-logo-${collapsed ? 't' : 'tdesign'}-logo`" />
<component :is="getLogo()" :class="logoCls" />
</span>
</template>
<menu-content :nav-data="menu" />
<template #operations>
<span class="version-container"> {{ !collapsed ? 'TDesign Starter' : '' }} {{ pgk.version }} </span>
<span :class="versionCls"> {{ !collapsed ? 'TDesign Starter' : '' }} {{ pgk.version }} </span>
</template>
</t-menu>
<div :class="`${prefix}-side-nav-placeholder${collapsed ? '-hidden' : ''}`"></div>
@ -26,7 +26,7 @@ import AssetLogo from '@/assets/assets-t-logo.svg?component';
import { prefix } from '@/config/global';
import { getActive, getRoutesExpanded } from '@/router';
import { useSettingStore } from '@/store';
import type { MenuRoute } from '@/types/interface';
import type { MenuRoute, ModeType } from '@/types/interface';
import pgk from '../../../package.json';
import MenuContent from './MenuContent.vue';
@ -55,7 +55,7 @@ const props = defineProps({
default: '64px',
},
theme: {
type: String as PropType<'light' | 'dark'>,
type: String as PropType<ModeType>,
default: 'light',
},
isCompact: {
@ -75,6 +75,10 @@ const defaultExpanded = computed(() => {
return union(expanded, parentPath === '' ? [] : [parentPath]);
});
const sideMode = computed(() => {
const { theme } = props;
return theme === 'dark';
});
const sideNavCls = computed(() => {
const { isCompact } = props;
return [
@ -84,7 +88,22 @@ const sideNavCls = computed(() => {
},
];
});
const logoCls = computed(() => {
return [
`${prefix}-side-nav-logo-${collapsed.value ? 't' : 'tdesign'}-logo`,
{
[`${prefix}-side-nav-dark`]: sideMode.value,
},
];
});
const versionCls = computed(() => {
return [
`version-container`,
{
[`${prefix}-side-nav-dark`]: sideMode.value,
},
];
});
const menuCls = computed(() => {
const { showLogo, isFixed, layout } = props;
return [

View File

@ -52,7 +52,6 @@
</t-popup>
</div>
</t-radio-group>
<div class="setting-group-title">{{ $t('layout.setting.navigationLayout') }}</div>
<t-radio-group v-model="formData.layout">
<div v-for="(item, index) in LAYOUT_OPTION" :key="index" class="setting-layout-drawer">
@ -62,15 +61,24 @@
</div>
</t-radio-group>
<t-form-item v-show="formData.layout === 'mix'" label="分割菜单(混合模式下有效)" name="splitMenu">
<t-form-item v-show="formData.layout === 'mix'" :label="$t('layout.setting.splitMenu')" name="splitMenu">
<t-switch v-model="formData.splitMenu" />
</t-form-item>
<t-form-item v-show="formData.layout === 'mix'" label="固定 Sidebar" name="isSidebarFixed">
<t-form-item
v-show="formData.layout === 'mix'"
:label="$t('layout.setting.fixedSidebar')"
name="isSidebarFixed"
>
<t-switch v-model="formData.isSidebarFixed" />
</t-form-item>
<div class="setting-group-title">{{ $t('layout.setting.element.title') }}</div>
<t-form-item :label="$t('layout.setting.sideMode')" name="sideMode">
<t-radio-group v-model="formData.sideMode" class="side-mode-radio">
<t-radio-button key="light" value="light" :label="$t('layout.setting.theme.options.light')" />
<t-radio-button key="dark" value="dark" :label="$t('layout.setting.theme.options.dark')" />
</t-radio-group>
</t-form-item>
<t-form-item
v-show="formData.layout === 'side'"
:label="$t('layout.setting.element.showHeader')"
@ -295,6 +303,10 @@ watchEffect(() => {
width: 100%;
justify-content: space-between;
align-items: center;
&.side-mode-radio {
justify-content: end;
}
}
.t-radio-group.t-size-m .t-radio-button {

View File

@ -33,6 +33,7 @@ export default {
},
},
navigationLayout: 'Navigation Layout',
sideMode: 'Side Menu Mode',
splitMenu: 'Split MenuOnly Mix mode',
fixedSidebar: 'Fixed Sidebar',
element: {

View File

@ -33,6 +33,7 @@ export default {
},
},
navigationLayout: '导航布局',
sideMode: '侧边栏模式',
splitMenu: '分割菜单(混合模式下有效)',
fixedSidebar: '固定侧边栏',
element: {

View File

@ -14,7 +14,7 @@
theme="primary"
mode="date"
:default-value="LAST_7_DAYS"
@change="onCurrencyChange"
@change="(value) => onCurrencyChange(value as string[])"
/>
</div>
</template>
@ -97,6 +97,25 @@ const renderCountChart = () => {
}
countChart = echarts.init(countContainer);
countChart.setOption(getPieChartDataSet(chartColors.value));
//
countChart.dispatchAction({
type: 'downplay',
seriesIndex: 0,
dataIndex: -1,
});
//
countChart.dispatchAction({
type: 'highlight',
seriesIndex: 0,
dataIndex: 1,
});
// tooltip
countChart.dispatchAction({
type: 'showTip',
seriesIndex: 0,
dataIndex: 1,
});
};
const renderCharts = () => {

View File

@ -14,7 +14,7 @@
theme="primary"
mode="date"
:default-value="LAST_7_DAYS"
@change="onStokeDataChange"
@change="(value) => onStokeDataChange(value as string[])"
/>
</template>
<div id="stokeContainer" style="width: 100%; height: 351px" class="dashboard-chart-container"></div>

View File

@ -367,8 +367,7 @@ export function getPieChartDataSet({
emphasis: {
scale: true,
label: {
show: true,
formatter: ['{value|{d}%}', '{name|{b}渠道占比}'].join('\n'),
show: false,
rich: {
value: {
color: textColor,

View File

@ -26,7 +26,7 @@
theme="primary"
mode="date"
style="width: 248px"
@change="onMaterialChange"
@change="(value) => onMaterialChange(value as string[])"
/>
</template>
<div id="lineContainer" style="width: 100%; height: 416px" />

View File

@ -1,9 +1,8 @@
<template>
<div class="detail-advanced">
<t-card :title="$t('pages.detailCard.baseInfo.title')" :bordered="false">
<div class="info-block">
<div v-for="(item, index) in BASE_INFO_DATA" :key="index" class="info-item">
<h1>{{ item.name }}</h1>
<t-card :bordered="false">
<t-descriptions :title="$t('pages.detailCard.baseInfo.title')">
<t-descriptions-item v-for="(item, index) in BASE_INFO_DATA" :key="index" :label="item.name">
<span
:class="{
['inProgress']: item.type && item.type.value === 'inProgress',
@ -13,8 +12,8 @@
<i v-if="item.type && item.type.key === 'contractStatus'" />
{{ item.value }}
</span>
</div>
</div>
</t-descriptions-item>
</t-descriptions>
</t-card>
<!-- 发票进度 -->
@ -113,8 +112,8 @@
<t-dialog v-model:visible="visible" :header="$t('pages.detailCard.baseInfo.title')" @confirm="onConfirm">
<template #body>
<div class="dialog-info-block">
<div v-for="(item, index) in BASE_INFO_DATA" :key="index" class="info-item">
<h1>{{ item.name }}</h1>
<t-descriptions :column="1">
<t-descriptions-item v-for="(item, index) in BASE_INFO_DATA" :key="index" :label="item.name">
<span
:class="{
['inProgress']: item.type && item.type.value === 'inProgress',
@ -124,7 +123,8 @@
<i v-if="item.type && item.type.key === 'contractStatus'" />
{{ item.value }}
</span>
</div>
</t-descriptions-item>
</t-descriptions>
</div>
</template>
</t-dialog>

View File

@ -1,51 +1,4 @@
.detail-base {
:deep(.t-card) {
padding: var(--td-comp-paddingTB-xxl) var(--td-comp-paddingLR-xxl);
}
:deep(.t-card__header) {
padding: 0;
margin-bottom: var(--td-comp-margin-m);
}
:deep(.t-card__body) {
padding: 0;
}
:deep(.t-card__title) {
font: var(--td-font-title-large);
font-weight: 400;
}
&-info-steps {
padding-top: var(--td-comp-margin-xl);
}
}
.info-block {
column-count: 2;
.info-item {
padding-top: var(--td-comp-margin-xxl);
display: flex;
color: var(--td-text-color-primary);
h1 {
width: 160px;
font: var(--td-font-body-medium);
color: var(--td-text-color-secondary);
font-weight: normal;
text-align: left;
@media (max-width: @screen-sm-max) {
width: 80px;
}
@media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
width: 120px;
}
}
.t-descriptions {
span {
overflow: hidden;
white-space: nowrap;
@ -73,40 +26,3 @@
}
}
}
}
.dialog-info-block {
.info-item {
padding: 12px 0;
display: flex;
h1 {
width: 84px;
font-family: var(--td-font-family);
font-size: 14px;
color: var(--td-text-color-secondary);
text-align: left;
line-height: 22px;
}
span {
margin-left: var(--td-comp-margin-xxl);
}
i {
display: inline-block;
width: 8px;
height: 8px;
border-radius: var(--td-radius-circle);
background: var(--td-success-color);
}
.green {
color: var(--td-success-color);
}
.blue {
color: var(--td-brand-color);
}
}
}

View File

@ -1,9 +1,8 @@
<template>
<div class="detail-base">
<t-card :title="$t('pages.detailBase.baseInfo.title')" :bordered="false">
<div class="info-block">
<div v-for="(item, index) in BASE_INFO_DATA" :key="index" class="info-item">
<h1>{{ item.name }}</h1>
<t-card :bordered="false">
<t-descriptions :title="$t('pages.detailBase.baseInfo.title')">
<t-descriptions-item v-for="(item, index) in BASE_INFO_DATA" :key="index" :label="item.name">
<span
:class="{
['inProgress']: item.type && item.type.value === 'inProgress',
@ -13,8 +12,8 @@
<i v-if="item.type && item.type.key === 'contractStatus'" />
{{ item.value }}
</span>
</div>
</div>
</t-descriptions-item>
</t-descriptions>
</t-card>
<t-card :title="$t('pages.detailBase.changelog.title')" class="container-base-margin-top" :bordered="false">

View File

@ -31,7 +31,7 @@
:header-affixed-top="headerAffixedTop"
@page-change="rehandlePageChange"
@change="rehandleChange"
@select-change="rehandleSelectChange"
@select-change="(value: number[]) => rehandleSelectChange(value)"
>
<template #status="{ row }">
<t-tag v-if="row.status === CONTRACT_STATUS.FAIL" theme="danger" variant="light">

View File

@ -74,7 +74,7 @@ const formVisible = ref(false);
const formData = ref({ ...INITIAL_DATA });
const textareaValue = ref('');
const onSubmit = ({ validateResult, firstError }: SubmitContext<FormData>) => {
const onSubmit = ({ validateResult, firstError }: SubmitContext) => {
if (!firstError) {
MessagePlugin.success('提交成功');
formVisible.value = false;

View File

@ -15,16 +15,11 @@
<t-icon name="ellipsis" />
</t-button>
</template>
<t-row class="content" justify="space-between">
<t-col v-for="(item, index) in USER_INFO_LIST" :key="index" class="contract" :span="item.span ?? 3">
<div class="contract-title">
{{ $t(item.title) }}
</div>
<div class="contract-detail">
<t-descriptions :column="4" item-layout="vertical">
<t-descriptions-item v-for="(item, index) in USER_INFO_LIST" :key="index" :label="$t(item.title)">
{{ item.content }}
</div>
</t-col>
</t-row>
</t-descriptions-item>
</t-descriptions>
</t-card>
<t-card class="content-container" :bordered="false">
@ -185,4 +180,8 @@ watch(
<style lang="less" scoped>
@import './index.less';
.t-descriptions {
margin-top: 24px;
}
</style>

View File

@ -1,5 +1,5 @@
import uniq from 'lodash/uniq';
import { createRouter, createWebHistory, RouteRecordRaw, useRoute } from 'vue-router';
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
const env = import.meta.env.MODE || 'development';
@ -59,7 +59,8 @@ export const getRoutesExpanded = () => {
};
export const getActive = (maxLevel = 3): string => {
const route = useRoute();
// 非组件内调用必须通过Router实例获取当前路由
const route = router.currentRoute.value;
if (!route.path) {
return '';

View File

@ -2,15 +2,16 @@ import keys from 'lodash/keys';
import { defineStore } from 'pinia';
import { Color } from 'tvision-color';
import { DARK_CHART_COLORS, LIGHT_CHART_COLORS } from '@/config/color';
import { DARK_CHART_COLORS, LIGHT_CHART_COLORS, TColorSeries } from '@/config/color';
import STYLE_CONFIG from '@/config/style';
import { store } from '@/store';
import { ModeType } from '@/types/interface';
import { generateColorMap, insertThemeStylesheet } from '@/utils/color';
const state = {
const state: Record<string, any> = {
...STYLE_CONFIG,
showSettingPanel: false,
colorList: {},
colorList: {} as TColorSeries,
chartColors: LIGHT_CHART_COLORS,
};
@ -23,7 +24,7 @@ export const useSettingStore = defineStore('setting', {
showSidebar: (state) => state.layout !== 'top',
showSidebarLogo: (state) => state.layout === 'side',
showHeaderLogo: (state) => state.layout !== 'side',
displayMode: (state): 'dark' | 'light' => {
displayMode: (state): ModeType => {
if (state.mode === 'auto') {
const media = window.matchMedia('(prefers-color-scheme:dark)');
if (media.matches) {
@ -31,20 +32,18 @@ export const useSettingStore = defineStore('setting', {
}
return 'light';
}
return state.mode as 'dark' | 'light';
return state.mode as ModeType;
},
displaySideMode: (state): ModeType => {
return state.sideMode as ModeType;
},
},
actions: {
async changeMode(mode: 'dark' | 'light' | 'auto') {
async changeMode(mode: ModeType | 'auto') {
let theme = mode;
if (mode === 'auto') {
const media = window.matchMedia('(prefers-color-scheme:dark)');
if (media.matches) {
theme = 'dark';
} else {
theme = 'light';
}
theme = this.getMediaColor();
}
const isDarkMode = theme === 'dark';
@ -52,6 +51,19 @@ export const useSettingStore = defineStore('setting', {
this.chartColors = isDarkMode ? DARK_CHART_COLORS : LIGHT_CHART_COLORS;
},
async changeSideMode(mode: ModeType) {
const isDarkMode = mode === 'dark';
document.documentElement.setAttribute('side-mode', isDarkMode ? 'dark' : '');
},
getMediaColor() {
const media = window.matchMedia('(prefers-color-scheme:dark)');
if (media.matches) {
return 'dark';
}
return 'light';
},
changeBrandTheme(brandTheme: string) {
const mode = this.displayMode;
// 以主题色加显示模式作为键
@ -64,20 +76,23 @@ export const useSettingStore = defineStore('setting', {
step: 10,
remainInput: false, // 是否保留输入 不保留会矫正不合适的主题色
});
colorMap = generateColorMap(brandTheme, newPalette, mode as 'light' | 'dark', brandColorIndex);
colorMap = generateColorMap(brandTheme, newPalette, mode, brandColorIndex);
this.colorList[colorKey] = colorMap;
}
// TODO 需要解决不停切换时有反复插入 style 的问题
insertThemeStylesheet(brandTheme, colorMap, mode as 'light' | 'dark');
insertThemeStylesheet(brandTheme, colorMap, mode);
document.documentElement.setAttribute('theme-color', brandTheme);
},
updateConfig(payload: Partial<TState>) {
for (const key in payload) {
if (payload[key as TStateKey] !== undefined) {
this[key] = payload[key as TStateKey];
this[key as TStateKey] = payload[key as TStateKey];
}
if (key === 'mode') {
this.changeMode(payload[key]);
this.changeMode(payload[key] as ModeType);
}
if (key === 'sideMode') {
this.changeSideMode(payload[key] as ModeType);
}
if (key === 'brandTheme') {
this.changeBrandTheme(payload[key]);

View File

@ -163,7 +163,7 @@
}
&-logo-tdesign-logo {
padding: 0 var(--td-comp-paddingLR-xl);
margin-right: var(--td-comp-margin-xxxl);
height: var(--td-comp-size-s);
width: 100%;
color: var(--td-text-color-primary);
@ -176,6 +176,10 @@
}
}
&-side-nav-dark {
color: var(--td-font-white-1) !important;
}
&-side-nav-placeholder {
flex: 1 1 232px;
min-width: 232px;

View File

@ -1,8 +1,6 @@
import type { TabValue } from 'tdesign-vue-next';
import { LocationQueryRaw, RouteRecordName } from 'vue-router';
import STYLE_CONFIG from '@/config/style';
export interface RouteMeta {
title?: string | Record<string, string>;
icon?: string;
@ -32,14 +30,6 @@ export interface MenuRoute {
export type ModeType = 'dark' | 'light';
export type SettingType = typeof STYLE_CONFIG;
export type ClassName = { [className: string]: any } | ClassName[] | string;
export type CommonObjType = {
[key: string]: string | number;
};
export interface UserInfo {
name: string;
roles: string[];

View File

@ -3,6 +3,7 @@ import trim from 'lodash/trim';
import { Color } from 'tvision-color';
import { TColorToken } from '@/config/color';
import { ModeType } from '@/types/interface';
/**
*
@ -58,12 +59,7 @@ export function changeChartsTheme(chartsList: echarts.EChartsType[]): void {
/**
*
*/
export function generateColorMap(
theme: string,
colorPalette: Array<string>,
mode: 'light' | 'dark',
brandColorIdx: number,
) {
export function generateColorMap(theme: string, colorPalette: Array<string>, mode: ModeType, brandColorIdx: number) {
const isDarkMode = mode === 'dark';
if (isDarkMode) {
@ -76,7 +72,7 @@ export function generateColorMap(
colorPalette[0] = `${colorPalette[brandColorIdx]}20`;
}
const colorMap = {
const colorMap: TColorToken = {
'--td-brand-color': colorPalette[brandColorIdx], // 主题色
'--td-brand-color-1': colorPalette[0], // light
'--td-brand-color-2': colorPalette[1], // focus
@ -95,7 +91,7 @@ export function generateColorMap(
/**
*
*/
export function insertThemeStylesheet(theme: string, colorMap: TColorToken, mode: 'light' | 'dark') {
export function insertThemeStylesheet(theme: string, colorMap: TColorToken, mode: ModeType) {
const isDarkMode = mode === 'dark';
const root = !isDarkMode ? `:root[theme-color='${theme}']` : `:root[theme-color='${theme}'][theme-mode='dark']`;

View File

@ -1,4 +1,4 @@
module.exports = {
export default {
defaultSeverity: 'error',
extends: ['stylelint-config-standard'],
rules: {

View File

@ -4,6 +4,7 @@
"module": "esnext",
"moduleResolution": "node",
"jsx": "preserve",
"jsxImportSource": "vue",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
@ -16,7 +17,11 @@
"paths": {
"@/*": ["src/*"]
},
"noImplicitAny": true
"noImplicitAny": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"alwaysStrict": true,
},
"include": [
"**/*.ts",