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 字符.. // 一行最多 120 字符..
printWidth: 120, printWidth: 120,
// 使用 2 个空格缩进 // 使用 2 个空格缩进

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ export default {
mode: 'light', mode: 'light',
layout: 'side', layout: 'side',
splitMenu: false, splitMenu: false,
sideMode: 'light',
isFooterAside: false, isFooterAside: false,
isSidebarFixed: true, isSidebarFixed: true,
isHeaderFixed: 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> <template>
<router-view v-if="!isRefreshing" v-slot="{ Component }"> <router-view v-if="!isRefreshing" v-slot="{ Component }">
<transition name="fade"> <transition name="fade" mode="out-in">
<keep-alive :include="aliveViews"> <keep-alive :include="aliveViews">
<component :is="Component" /> <component :is="Component" />
</keep-alive> </keep-alive>
@ -54,7 +54,7 @@ const isRefreshing = computed(() => {
transition: opacity @anim-duration-slow @anim-time-fn-easing; transition: opacity @anim-duration-slow @anim-time-fn-easing;
} }
.fade-enter, .fade-enter-from,
.fade-leave-to { .fade-leave-to {
opacity: 0; opacity: 0;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -52,7 +52,6 @@
</t-popup> </t-popup>
</div> </div>
</t-radio-group> </t-radio-group>
<div class="setting-group-title">{{ $t('layout.setting.navigationLayout') }}</div> <div class="setting-group-title">{{ $t('layout.setting.navigationLayout') }}</div>
<t-radio-group v-model="formData.layout"> <t-radio-group v-model="formData.layout">
<div v-for="(item, index) in LAYOUT_OPTION" :key="index" class="setting-layout-drawer"> <div v-for="(item, index) in LAYOUT_OPTION" :key="index" class="setting-layout-drawer">
@ -62,15 +61,24 @@
</div> </div>
</t-radio-group> </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-switch v-model="formData.splitMenu" />
</t-form-item> </t-form-item>
<t-form-item
<t-form-item v-show="formData.layout === 'mix'" label="固定 Sidebar" name="isSidebarFixed"> v-show="formData.layout === 'mix'"
:label="$t('layout.setting.fixedSidebar')"
name="isSidebarFixed"
>
<t-switch v-model="formData.isSidebarFixed" /> <t-switch v-model="formData.isSidebarFixed" />
</t-form-item> </t-form-item>
<div class="setting-group-title">{{ $t('layout.setting.element.title') }}</div> <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 <t-form-item
v-show="formData.layout === 'side'" v-show="formData.layout === 'side'"
:label="$t('layout.setting.element.showHeader')" :label="$t('layout.setting.element.showHeader')"
@ -295,6 +303,10 @@ watchEffect(() => {
width: 100%; width: 100%;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
&.side-mode-radio {
justify-content: end;
}
} }
.t-radio-group.t-size-m .t-radio-button { .t-radio-group.t-size-m .t-radio-button {

View File

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

View File

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

View File

@ -14,7 +14,7 @@
theme="primary" theme="primary"
mode="date" mode="date"
:default-value="LAST_7_DAYS" :default-value="LAST_7_DAYS"
@change="onCurrencyChange" @change="(value) => onCurrencyChange(value as string[])"
/> />
</div> </div>
</template> </template>
@ -97,6 +97,25 @@ const renderCountChart = () => {
} }
countChart = echarts.init(countContainer); countChart = echarts.init(countContainer);
countChart.setOption(getPieChartDataSet(chartColors.value)); 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 = () => { const renderCharts = () => {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,112 +1,28 @@
.detail-base { .t-descriptions {
: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;
}
}
span { span {
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
margin-left: var(--td-comp-margin-xxl); margin-left: var(--td-comp-margin-xxl);
} }
i { i {
display: inline-block; display: inline-block;
width: 8px; width: 8px;
height: 8px; height: 8px;
border-radius: var(--td-radius-circle); border-radius: var(--td-radius-circle);
background: var(--td-success-color); background: var(--td-success-color);
} }
.inProgress { .inProgress {
color: var(--td-success-color); color: var(--td-success-color);
} }
.pdf { .pdf {
color: var(--td-brand-color); color: var(--td-brand-color);
&:hover { &:hover {
cursor: pointer; cursor: pointer;
} }
} }
}
}
.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> <template>
<div class="detail-base"> <div class="detail-base">
<t-card :title="$t('pages.detailBase.baseInfo.title')" :bordered="false"> <t-card :bordered="false">
<div class="info-block"> <t-descriptions :title="$t('pages.detailBase.baseInfo.title')">
<div v-for="(item, index) in BASE_INFO_DATA" :key="index" class="info-item"> <t-descriptions-item v-for="(item, index) in BASE_INFO_DATA" :key="index" :label="item.name">
<h1>{{ item.name }}</h1>
<span <span
:class="{ :class="{
['inProgress']: item.type && item.type.value === 'inProgress', ['inProgress']: item.type && item.type.value === 'inProgress',
@ -13,8 +12,8 @@
<i v-if="item.type && item.type.key === 'contractStatus'" /> <i v-if="item.type && item.type.key === 'contractStatus'" />
{{ item.value }} {{ item.value }}
</span> </span>
</div> </t-descriptions-item>
</div> </t-descriptions>
</t-card> </t-card>
<t-card :title="$t('pages.detailBase.changelog.title')" class="container-base-margin-top" :bordered="false"> <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" :header-affixed-top="headerAffixedTop"
@page-change="rehandlePageChange" @page-change="rehandlePageChange"
@change="rehandleChange" @change="rehandleChange"
@select-change="rehandleSelectChange" @select-change="(value: number[]) => rehandleSelectChange(value)"
> >
<template #status="{ row }"> <template #status="{ row }">
<t-tag v-if="row.status === CONTRACT_STATUS.FAIL" theme="danger" variant="light"> <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 formData = ref({ ...INITIAL_DATA });
const textareaValue = ref(''); const textareaValue = ref('');
const onSubmit = ({ validateResult, firstError }: SubmitContext<FormData>) => { const onSubmit = ({ validateResult, firstError }: SubmitContext) => {
if (!firstError) { if (!firstError) {
MessagePlugin.success('提交成功'); MessagePlugin.success('提交成功');
formVisible.value = false; formVisible.value = false;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import trim from 'lodash/trim';
import { Color } from 'tvision-color'; import { Color } from 'tvision-color';
import { TColorToken } from '@/config/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( export function generateColorMap(theme: string, colorPalette: Array<string>, mode: ModeType, brandColorIdx: number) {
theme: string,
colorPalette: Array<string>,
mode: 'light' | 'dark',
brandColorIdx: number,
) {
const isDarkMode = mode === 'dark'; const isDarkMode = mode === 'dark';
if (isDarkMode) { if (isDarkMode) {
@ -76,7 +72,7 @@ export function generateColorMap(
colorPalette[0] = `${colorPalette[brandColorIdx]}20`; colorPalette[0] = `${colorPalette[brandColorIdx]}20`;
} }
const colorMap = { const colorMap: TColorToken = {
'--td-brand-color': colorPalette[brandColorIdx], // 主题色 '--td-brand-color': colorPalette[brandColorIdx], // 主题色
'--td-brand-color-1': colorPalette[0], // light '--td-brand-color-1': colorPalette[0], // light
'--td-brand-color-2': colorPalette[1], // focus '--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 isDarkMode = mode === 'dark';
const root = !isDarkMode ? `:root[theme-color='${theme}']` : `:root[theme-color='${theme}'][theme-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', defaultSeverity: 'error',
extends: ['stylelint-config-standard'], extends: ['stylelint-config-standard'],
rules: { rules: {

View File

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