Merge branch 'develop' of github.com:Tencent/tdesign-vue-next-starter into main

This commit is contained in:
Uyarn 2022-12-19 18:20:41 +08:00
commit 5797d6926a
35 changed files with 393 additions and 75 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "tdesign-vue-next-starter", "name": "tdesign-vue-next-starter",
"version": "0.6.0", "version": "0.6.1",
"scripts": { "scripts": {
"dev:mock": "vite --open --mode mock", "dev:mock": "vite --open --mode mock",
"dev": "vite --open --mode development", "dev": "vite --open --mode development",
@ -24,11 +24,11 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.0.11", "pinia": "^2.0.11",
"pinia-plugin-persistedstate": "^2.1.1", "pinia-plugin-persistedstate": "^3.0.1",
"qrcode.vue": "^3.2.2", "qrcode.vue": "^3.2.2",
"qs": "^6.10.5", "qs": "^6.10.5",
"tdesign-icons-vue-next": "^0.1.1", "tdesign-icons-vue-next": "^0.1.1",
"tdesign-vue-next": "^0.24.9", "tdesign-vue-next": "^0.26.2",
"tvision-color": "^1.3.1", "tvision-color": "^1.3.1",
"vue": "^3.2.31", "vue": "^3.2.31",
"vue-clipboard3": "^2.0.0", "vue-clipboard3": "^2.0.0",

View File

@ -55,7 +55,7 @@
</t-card> </t-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { PropType } from 'vue'; import type { PropType } from 'vue';
import { import {
ShopIcon, ShopIcon,
CalendarIcon, CalendarIcon,

View File

@ -0,0 +1,34 @@
import debounce from 'lodash/debounce';
import { onUnmounted, onMounted } 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

@ -6,11 +6,14 @@
</keep-alive> </keep-alive>
</transition> </transition>
</router-view> </router-view>
<frame-page />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ComputedRef } from 'vue'; import { computed } from 'vue';
import type { ComputedRef } from 'vue';
import { useTabsRouterStore } from '@/store'; import { useTabsRouterStore } from '@/store';
import FramePage from '@/layouts/frame/index.vue';
// <suspense>使 // <suspense>使
// /page/1=> /page/2 使activeRouteFullPath key // /page/1=> /page/2 使activeRouteFullPath key

View File

@ -0,0 +1,10 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'FrameBlank',
});
</script>

View File

@ -0,0 +1,99 @@
<template>
<div :class="prefixCls" :style="getWrapStyle">
<t-loading :loading="loading" size="large" :style="getWrapStyle">
<iframe ref="frameRef" :src="frameSrc" :class="`${prefixCls}__main`" @load="hideLoading"></iframe>
</t-loading>
</div>
</template>
<script lang="ts" setup>
import { CSSProperties, watch, ref, unref, computed } from 'vue';
import debounce from 'lodash/debounce';
import { useWindowSizeFn } from '@/hooks/event/useWindowSizeFn';
import { prefix } from '@/config/global';
import { useSettingStore } from '@/store';
defineProps({
frameSrc: String,
});
const loading = ref(true);
const heightRef = ref(window.innerHeight);
const frameRef = ref<HTMLFrameElement>();
const prefixCls = computed(() => [`${prefix}-iframe-page`]);
const settingStore = useSettingStore();
const getWrapStyle = computed((): CSSProperties => {
return {
height: `${unref(heightRef)}px`,
};
});
const computedStyle = getComputedStyle(document.documentElement);
const sizeXxxl = computedStyle.getPropertyValue('--td-comp-size-xxxl');
const paddingTBXxl = computedStyle.getPropertyValue('--td-comp-paddingTB-xxl');
function getOuterHeight(dom: Element) {
let height = dom.clientHeight;
const computedStyle = window.getComputedStyle(dom);
height += parseInt(computedStyle.marginTop, 10);
height += parseInt(computedStyle.marginBottom, 10);
height += parseInt(computedStyle.borderTopWidth, 10);
height += parseInt(computedStyle.borderBottomWidth, 10);
return height;
}
function calcHeight() {
const iframe = unref(frameRef);
if (!iframe) {
return;
}
let clientHeight = 0;
const { showFooter, isUseTabsRouter, showBreadcrumb } = settingStore;
const headerHeight = parseFloat(sizeXxxl);
const navDom = document.querySelector('.t-tabs__nav');
const navHeight = isUseTabsRouter ? getOuterHeight(navDom) : 0;
const breadcrumbDom = document.querySelector('.t-breadcrumb');
const breadcrumbHeight = showBreadcrumb ? getOuterHeight(breadcrumbDom) : 0;
const contentPadding = parseFloat(paddingTBXxl) * 2;
const footerDom = document.querySelector('.t-layout__footer');
const footerHeight = showFooter ? getOuterHeight(footerDom) : 0;
const top = headerHeight + navHeight + breadcrumbHeight + contentPadding + footerHeight + 2;
heightRef.value = window.innerHeight - top;
clientHeight = document.documentElement.clientHeight - top;
iframe.style.height = `${clientHeight}px`;
}
function hideLoading() {
loading.value = false;
calcHeight();
}
useWindowSizeFn(calcHeight, { immediate: true });
watch(
[() => settingStore.showFooter, () => settingStore.isUseTabsRouter, () => settingStore.showBreadcrumb],
debounce(calcHeight, 250),
);
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{starter-prefix}-iframe-page';
.@{prefix-cls} {
&__mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
&__main {
width: 100%;
height: 100%;
overflow: hidden;
background-color: #fff;
border: 0;
box-sizing: border-box;
}
}
</style>

View File

@ -64,13 +64,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { PropType, computed } from 'vue'; import { computed } from 'vue';
import type { PropType } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useSettingStore } from '@/store'; import { useSettingStore } from '@/store';
import { getActive } from '@/router'; import { getActive } from '@/router';
import { prefix } from '@/config/global'; import { prefix } from '@/config/global';
import LogoFull from '@/assets/assets-logo-full.svg?component'; import LogoFull from '@/assets/assets-logo-full.svg?component';
import { MenuRoute } from '@/types/interface'; import type { MenuRoute } from '@/types/interface';
import Notice from './Notice.vue'; import Notice from './Notice.vue';
import Search from './Search.vue'; import Search from './Search.vue';
@ -143,7 +144,10 @@ const handleNav = (url) => {
}; };
const handleLogout = () => { const handleLogout = () => {
router.push(`/login?redirect=${router.currentRoute.value.fullPath}`); router.push({
path: '/login',
query: { redirect: encodeURIComponent(router.currentRoute.value.fullPath) },
});
}; };
const navToGitHub = () => { const navToGitHub = () => {

View File

@ -21,7 +21,7 @@
:min-column-width="128" :min-column-width="128"
:popup-props="{ :popup-props="{
overlayClassName: 'route-tabs-dropdown', overlayClassName: 'route-tabs-dropdown',
onVisibleChange: (visible: boolean, ctx) => handleTabMenuClick(visible, ctx, routeItem.path), onVisibleChange: (visible, ctx) => handleTabMenuClick(visible, ctx, routeItem.path),
visible: activeTabPath === routeItem.path, visible: activeTabPath === routeItem.path,
}" }"
> >
@ -71,7 +71,7 @@ import { nextTick, ref, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useSettingStore, useTabsRouterStore } from '@/store'; import { useSettingStore, useTabsRouterStore } from '@/store';
import { prefix } from '@/config/global'; import { prefix } from '@/config/global';
import { TRouterInfo } from '@/types/interface'; import type { TRouterInfo } from '@/types/interface';
import LContent from './Content.vue'; import LContent from './Content.vue';
import LBreadcrumb from './Breadcrumb.vue'; import LBreadcrumb from './Breadcrumb.vue';

View File

@ -2,7 +2,7 @@
<div> <div>
<template v-for="item in list" :key="item.path"> <template v-for="item in list" :key="item.path">
<template v-if="!item.children || !item.children.length || item.meta?.single"> <template v-if="!item.children || !item.children.length || item.meta?.single">
<t-menu-item v-if="getHref(item)" :href="getHref(item)?.[0]" :name="item.path" :value="getPath(item)"> <t-menu-item v-if="getHref(item)" :name="item.path" :value="getPath(item)" @click="openHref(getHref(item)[0])">
<template #icon> <template #icon>
<t-icon v-if="beIcon(item)" :name="item.icon" /> <t-icon v-if="beIcon(item)" :name="item.icon" />
<component :is="beRender(item).render" v-else-if="beRender(item).can" class="t-icon" /> <component :is="beRender(item).render" v-else-if="beRender(item).can" class="t-icon" />
@ -29,9 +29,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, PropType } from 'vue'; import { computed } from 'vue';
import type { PropType } from 'vue';
import isObject from 'lodash/isObject'; import isObject from 'lodash/isObject';
import { MenuRoute } from '@/types/interface'; import type { MenuRoute } from '@/types/interface';
import { getActive } from '@/router'; import { getActive } from '@/router';
const props = defineProps({ const props = defineProps({
@ -47,7 +48,9 @@ const list = computed(() => {
return getMenuList(navData); return getMenuList(navData);
}); });
const getMenuList = (list: MenuRoute[], basePath?: string): MenuRoute[] => { type ListItemType = MenuRoute & { icon?: string };
const getMenuList = (list: MenuRoute[], basePath?: string): ListItemType[] => {
if (!list) { if (!list) {
return []; return [];
} }
@ -71,7 +74,11 @@ const getMenuList = (list: MenuRoute[], basePath?: string): MenuRoute[] => {
}; };
const getHref = (item: MenuRoute) => { const getHref = (item: MenuRoute) => {
return item.path.match(/(http|https):\/\/([\w.]+\/?)\S*/); const { frameSrc, frameBlank } = item.meta;
if (frameSrc && frameBlank) {
return frameSrc.match(/(http|https):\/\/([\w.]+\/?)\S*/);
}
return null;
}; };
const getPath = (item) => { const getPath = (item) => {
@ -97,6 +104,10 @@ const beRender = (item: MenuRoute) => {
render: null, render: null,
}; };
}; };
const openHref = (url: string) => {
window.open(url);
};
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -49,7 +49,7 @@
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useNotificationStore } from '@/store'; import { useNotificationStore } from '@/store';
import { NotificationItem } from '@/types/interface'; import type { NotificationItem } from '@/types/interface';
const router = useRouter(); const router = useRouter();
const store = useNotificationStore(); const store = useNotificationStore();

View File

@ -16,14 +16,15 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, PropType } from 'vue'; import { computed, onMounted } from 'vue';
import type { PropType } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import union from 'lodash/union'; import union from 'lodash/union';
import { useSettingStore } from '@/store'; import { useSettingStore } from '@/store';
import { prefix } from '@/config/global'; import { prefix } from '@/config/global';
import pgk from '../../../package.json'; import pgk from '../../../package.json';
import { MenuRoute } from '@/types/interface'; import type { MenuRoute } from '@/types/interface';
import { getActive, getRoutesExpanded } from '@/router'; import { getActive, getRoutesExpanded } from '@/router';
import AssetLogo from '@/assets/assets-t-logo.svg?component'; import AssetLogo from '@/assets/assets-t-logo.svg?component';
@ -54,7 +55,7 @@ const props = defineProps({
default: '64px', default: '64px',
}, },
theme: { theme: {
type: String as PropType<string>, type: String as PropType<'light' | 'dark'>,
default: 'light', default: 'light',
}, },
isCompact: { isCompact: {

View File

@ -0,0 +1,25 @@
<template>
<div v-if="showFrame">
<template v-for="frame in getFramePages" :key="frame.path">
<frame-content v-if="hasRenderFrame(frame.name)" v-show="showIframe(frame)" :frame-src="frame.meta.frameSrc" />
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, unref, computed } from 'vue';
import FrameContent from '../components/FrameContent.vue';
import { useFrameKeepAlive } from './useFrameKeepAlive';
export default defineComponent({
name: 'FrameLayout',
components: { FrameContent },
setup() {
const { getFramePages, hasRenderFrame, showIframe } = useFrameKeepAlive();
const showFrame = computed(() => unref(getFramePages).length > 0);
return { getFramePages, hasRenderFrame, showIframe, showFrame };
},
});
</script>

View File

@ -0,0 +1,53 @@
import { computed, toRaw, unref } from 'vue';
import uniqBy from 'lodash/uniqBy';
import { useRouter } from 'vue-router';
import { useSettingStore, useTabsRouterStore } from '@/store';
import type { MenuRoute } from '@/types/interface';
export function useFrameKeepAlive() {
const router = useRouter();
const { currentRoute } = router;
const { isUseTabsRouter } = useSettingStore();
const tabStore = useTabsRouterStore();
const getFramePages = computed(() => {
const ret = getAllFramePages(toRaw(router.getRoutes()) as unknown as MenuRoute[]) || [];
return ret;
});
const getOpenTabList = computed((): string[] => {
return tabStore.tabRouters.reduce((prev: string[], next) => {
if (next.meta && Reflect.has(next.meta, 'frameSrc')) {
prev.push(next.name as string);
}
return prev;
}, []);
});
function getAllFramePages(routes: MenuRoute[]): MenuRoute[] {
let res: MenuRoute[] = [];
for (const route of routes) {
const { meta: { frameSrc, frameBlank } = {}, children } = route;
if (frameSrc && !frameBlank) {
res.push(route);
}
if (children && children.length) {
res.push(...getAllFramePages(children));
}
}
res = uniqBy(res, 'name');
return res;
}
function showIframe(item: MenuRoute) {
return item.name === unref(currentRoute).name;
}
function hasRenderFrame(name: string) {
if (!unref(isUseTabsRouter)) {
return router.currentRoute.value.name === name;
}
return unref(getOpenTabList).includes(name);
}
return { hasRenderFrame, getFramePages, showIframe, getAllFramePages };
}

View File

@ -56,7 +56,7 @@ const appendNewRoute = () => {
meta: { title }, meta: { title },
name, name,
} = route; } = route;
tabsRouterStore.appendTabRouterList({ path, query, title: title as string, name, isAlive: true }); tabsRouterStore.appendTabRouterList({ path, query, title: title as string, name, isAlive: true, meta: route.meta });
}; };
onMounted(() => { onMounted(() => {

View File

@ -100,7 +100,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, watchEffect } from 'vue'; import { ref, computed, onMounted, watchEffect } from 'vue';
import { MessagePlugin, PopupVisibleChangeContext } from 'tdesign-vue-next'; import { MessagePlugin } from 'tdesign-vue-next';
import type { PopupVisibleChangeContext } from 'tdesign-vue-next';
import { Color } from 'tvision-color'; import { Color } from 'tvision-color';
import useClipboard from 'vue-clipboard3'; import useClipboard from 'vue-clipboard3';

View File

@ -119,8 +119,8 @@ export const TABLE_COLUMNS_DATA = [
sorter: (a, b) => Date.parse(a.updateTime) - Date.parse(b.updateTime), sorter: (a, b) => Date.parse(a.updateTime) - Date.parse(b.updateTime),
}, },
{ {
align: 'left', align: 'left' as const,
fixed: 'right', fixed: 'right' as const,
width: 200, width: 200,
className: 'test2', className: 'test2',
colKey: 'op', colKey: 'op',

View File

@ -104,12 +104,12 @@ export const TABLE_COLUMNS = [
sorter: (a, b) => Date.parse(a.updateTime) - Date.parse(b.updateTime), sorter: (a, b) => Date.parse(a.updateTime) - Date.parse(b.updateTime),
}, },
{ {
align: 'left', align: 'left' as const,
width: '200', width: '200',
className: 'test2', className: 'test2',
ellipsis: true, ellipsis: true,
colKey: 'op', colKey: 'op',
fixed: 'right', fixed: 'right' as const,
title: '操作', title: '操作',
}, },
]; ];

View File

@ -60,7 +60,7 @@ export default {
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { NOTIFICATION_TYPES } from '@/constants'; import { NOTIFICATION_TYPES } from '@/constants';
import { NotificationItem } from '@/types/interface'; import type { NotificationItem } from '@/types/interface';
import EmptyIcon from '@/assets/assets-empty.svg?component'; import EmptyIcon from '@/assets/assets-empty.svg?component';
import { useNotificationStore } from '@/store'; import { useNotificationStore } from '@/store';

View File

@ -1,4 +1,6 @@
export const FORM_RULES = { import { FormRule } from 'tdesign-vue-next';
export const FORM_RULES: Record<string, FormRule[]> = {
name: [{ required: true, message: '请输入合同名称', type: 'error' }], name: [{ required: true, message: '请输入合同名称', type: 'error' }],
type: [{ required: true, message: '请选择合同类型', type: 'error' }], type: [{ required: true, message: '请选择合同类型', type: 'error' }],
payment: [{ required: true, message: '请选择合同收付类型', type: 'error' }], payment: [{ required: true, message: '请选择合同收付类型', type: 'error' }],

View File

@ -1,4 +1,6 @@
export const FORM_RULES = { import { FormRule } from 'tdesign-vue-next';
export const FORM_RULES: Record<string, FormRule[]> = {
name: [{ required: true, message: '请选择合同名称', type: 'error' }], name: [{ required: true, message: '请选择合同名称', type: 'error' }],
type: [{ required: true, message: '请选择发票类型', type: 'error' }], type: [{ required: true, message: '请选择发票类型', type: 'error' }],
title: [{ required: true, message: '请输入发票抬头', type: 'error' }], title: [{ required: true, message: '请输入发票抬头', type: 'error' }],

View File

@ -149,7 +149,7 @@ export default {
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { ValidateResultContext } from 'tdesign-vue-next'; import { SubmitContext, Data } from 'tdesign-vue-next';
import { import {
FORM_RULES, FORM_RULES,
@ -179,7 +179,7 @@ const amount = computed(() => {
return '--'; return '--';
}); });
const onSubmit = (result: ValidateResultContext<FormData>, val: number) => { const onSubmit = (result: SubmitContext<Data>, val: number) => {
if (result.validateResult === true) { if (result.validateResult === true) {
activeForm.value = val; activeForm.value = val;
} }

View File

@ -1,4 +1,6 @@
export const COLUMNS = [ import { PrimaryTableCol, TableRowData } from 'tdesign-vue-next';
export const COLUMNS: PrimaryTableCol<TableRowData>[] = [
{ colKey: 'row-select', type: 'multiple', width: 64, fixed: 'left' }, { colKey: 'row-select', type: 'multiple', width: 64, fixed: 'left' },
{ {
title: '合同名称', title: '合同名称',
@ -7,7 +9,7 @@ export const COLUMNS = [
colKey: 'name', colKey: 'name',
fixed: 'left', fixed: 'left',
}, },
{ title: '合同状态', colKey: 'status', width: 200, cell: { col: 'status' } }, { title: '合同状态', colKey: 'status', width: 200 },
{ {
title: '合同编号', title: '合同编号',
width: 200, width: 200,

View File

@ -24,7 +24,7 @@
:pagination="pagination" :pagination="pagination"
:selected-row-keys="selectedRowKeys" :selected-row-keys="selectedRowKeys"
:loading="dataLoading" :loading="dataLoading"
:header-affixed-top="{ offsetTop, container: getContainer }" :header-affixed-top="headerAffixedTop"
@page-change="rehandlePageChange" @page-change="rehandlePageChange"
@change="rehandleChange" @change="rehandleChange"
@select-change="rehandleSelectChange" @select-change="rehandleSelectChange"
@ -177,13 +177,13 @@ const handleClickDelete = (row: { rowIndex: any }) => {
confirmVisible.value = true; confirmVisible.value = true;
}; };
const offsetTop = computed(() => { const headerAffixedTop = computed(
return store.isUseTabsRouter ? 48 : 0; () =>
}); ({
offsetTop: store.isUseTabsRouter ? 48 : 0,
const getContainer = () => { container: `.${prefix}-layout`,
return document.querySelector(`.${prefix}-layout`); } as any),
}; );
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -36,7 +36,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next'; import { MessagePlugin, FormRule, SubmitContext, Data } from 'tdesign-vue-next';
const INITIAL_DATA = { const INITIAL_DATA = {
name: '', name: '',
@ -70,12 +70,12 @@ const formVisible = ref(false);
const formData = ref(props.data); const formData = ref(props.data);
const textareaValue = ref(''); const textareaValue = ref('');
const onSubmit = ({ result, firstError }) => { const onSubmit = ({ validateResult, firstError }: SubmitContext<Data>) => {
if (!firstError) { if (!firstError) {
MessagePlugin.success('提交成功'); MessagePlugin.success('提交成功');
formVisible.value = false; formVisible.value = false;
} else { } else {
console.log('Errors: ', result); console.log('Errors: ', validateResult);
MessagePlugin.warning(firstError); MessagePlugin.warning(firstError);
} }
}; };
@ -107,7 +107,7 @@ watch(
}, },
); );
const rules = { const rules: Record<string, FormRule[]> = {
name: [{ required: true, message: '请输入产品名称', type: 'error' }], name: [{ required: true, message: '请输入产品名称', type: 'error' }],
}; };
</script> </script>

View File

@ -73,7 +73,7 @@
:hover="hover" :hover="hover"
:pagination="pagination" :pagination="pagination"
:loading="dataLoading" :loading="dataLoading"
:header-affixed-top="{ offsetTop, container: getContainer }" :header-affixed-top="headerAffixedTop"
@page-change="rehandlePageChange" @page-change="rehandlePageChange"
@change="rehandleChange" @change="rehandleChange"
> >
@ -114,7 +114,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next'; import { MessagePlugin, PrimaryTableCol, TableRowData, PageInfo } from 'tdesign-vue-next';
import Trend from '@/components/trend/index.vue'; import Trend from '@/components/trend/index.vue';
import { getList } from '@/api/list'; import { getList } from '@/api/list';
import { useSettingStore } from '@/store'; import { useSettingStore } from '@/store';
@ -130,7 +130,7 @@ import {
const store = useSettingStore(); const store = useSettingStore();
const COLUMNS = [ const COLUMNS: PrimaryTableCol<TableRowData>[] = [
{ {
title: '合同名称', title: '合同名称',
fixed: 'left', fixed: 'left',
@ -139,7 +139,7 @@ const COLUMNS = [
align: 'left', align: 'left',
colKey: 'name', colKey: 'name',
}, },
{ title: '合同状态', colKey: 'status', width: 200, cell: { col: 'status' } }, { title: '合同状态', colKey: 'status', width: 200 },
{ {
title: '合同编号', title: '合同编号',
width: 200, width: 200,
@ -182,7 +182,7 @@ const searchForm = {
const formData = ref({ ...searchForm }); const formData = ref({ ...searchForm });
const rowKey = 'index'; const rowKey = 'index';
const verticalAlign = 'top'; const verticalAlign = 'top' as const;
const hover = true; const hover = true;
const pagination = ref({ const pagination = ref({
@ -251,8 +251,8 @@ const onReset = (val) => {
const onSubmit = (val) => { const onSubmit = (val) => {
console.log(val); console.log(val);
}; };
const rehandlePageChange = (curr, pageInfo) => { const rehandlePageChange = (pageInfo: PageInfo, newDataSource: TableRowData[]) => {
console.log('分页变化', curr, pageInfo); console.log('分页变化', pageInfo, newDataSource);
}; };
const rehandleChange = (changeParams, triggerAndData) => { const rehandleChange = (changeParams, triggerAndData) => {
console.log('统一Change', changeParams, triggerAndData); console.log('统一Change', changeParams, triggerAndData);
@ -261,13 +261,13 @@ const rehandleClickOp = ({ text, row }) => {
console.log(text, row); console.log(text, row);
}; };
const offsetTop = computed(() => { const headerAffixedTop = computed(
return store.isUseTabsRouter ? 48 : 0; () =>
}); ({
offsetTop: store.isUseTabsRouter ? 48 : 0,
const getContainer = () => { container: `.${prefix}-layout`,
return document.querySelector(`.${prefix}-layout`); } as any), // TO BE FIXED
}; );
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -80,9 +80,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import QrcodeVue from 'qrcode.vue'; import QrcodeVue from 'qrcode.vue';
import { FormInstanceFunctions, MessagePlugin } from 'tdesign-vue-next'; import { MessagePlugin } from 'tdesign-vue-next';
import type { FormInstanceFunctions, FormRule } from 'tdesign-vue-next';
import { useCounter } from '@/hooks'; import { useCounter } from '@/hooks';
import { useUserStore } from '@/store'; import { useUserStore } from '@/store';
@ -96,7 +97,7 @@ const INITIAL_DATA = {
checked: false, checked: false,
}; };
const FORM_RULES = { const FORM_RULES: Record<string, FormRule[]> = {
phone: [{ required: true, message: '手机号必填', type: 'error' }], phone: [{ required: true, message: '手机号必填', type: 'error' }],
account: [{ required: true, message: '账号必填', type: 'error' }], account: [{ required: true, message: '账号必填', type: 'error' }],
password: [{ required: true, message: '密码必填', type: 'error' }], password: [{ required: true, message: '密码必填', type: 'error' }],
@ -116,6 +117,7 @@ const switchType = (val: string) => {
}; };
const router = useRouter(); const router = useRouter();
const route = useRoute();
/** /**
* 发送验证码 * 发送验证码
@ -134,9 +136,9 @@ const onSubmit = async ({ validateResult }) => {
await userStore.login(formData.value); await userStore.login(formData.value);
MessagePlugin.success('登陆成功'); MessagePlugin.success('登陆成功');
router.push({ const redirect = route.query.redirect as string;
path: '/dashboard/base', const redirectUrl = redirect ? decodeURIComponent(redirect) : '/dashboard';
}); router.push(redirectUrl);
} catch (e) { } catch (e) {
console.log(e); console.log(e);
MessagePlugin.error(e.message); MessagePlugin.error(e.message);

View File

@ -72,7 +72,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next'; import { MessagePlugin, FormRule } from 'tdesign-vue-next';
import { useCounter } from '@/hooks'; import { useCounter } from '@/hooks';
const INITIAL_DATA = { const INITIAL_DATA = {
@ -83,7 +83,7 @@ const INITIAL_DATA = {
checked: false, checked: false,
}; };
const FORM_RULES = { const FORM_RULES: Record<string, FormRule[]> = {
phone: [{ required: true, message: '手机号必填', type: 'error' }], phone: [{ required: true, message: '手机号必填', type: 'error' }],
email: [ email: [
{ required: true, message: '邮箱必填', type: 'error' }, { required: true, message: '邮箱必填', type: 'error' },

View File

@ -40,7 +40,10 @@ router.beforeEach(async (to, from, next) => {
} }
} catch (error) { } catch (error) {
MessagePlugin.error(error); MessagePlugin.error(error);
next(`/login?redirect=${to.path}`); next({
path: '/login',
query: { redirect: encodeURIComponent(to.fullPath) },
});
NProgress.done(); NProgress.done();
} }
} }
@ -49,7 +52,10 @@ router.beforeEach(async (to, from, next) => {
if (whiteListRouters.indexOf(to.path) !== -1) { if (whiteListRouters.indexOf(to.path) !== -1) {
next(); next();
} else { } else {
next(`/login?redirect=${to.path}`); next({
path: '/login',
query: { redirect: encodeURIComponent(to.fullPath) },
});
} }
NProgress.done(); NProgress.done();
} }

View File

@ -0,0 +1,47 @@
import Layout from '@/layouts/index.vue';
const IFrame = () => import('@/layouts/components/FrameBlank.vue');
export default [
{
path: '/frame',
name: 'Frame',
component: Layout,
redirect: '/frame/doc',
meta: {
icon: 'internet',
title: '外部页面',
},
children: [
{
path: 'doc',
name: 'Doc',
component: IFrame,
meta: {
frameSrc: 'https://tdesign.tencent.com/starter/docs/vue-next/get-started',
title: '使用文档(内嵌)',
},
},
{
path: 'TDesign',
name: 'TDesign',
component: IFrame,
meta: {
frameSrc: 'https://tdesign.tencent.com/vue-next/getting-started',
title: 'TDesign 文档(内嵌)',
},
},
{
path: 'TDesign2',
name: 'TDesign2',
component: IFrame,
meta: {
frameSrc: 'https://tdesign.tencent.com/vue-next/getting-started',
frameBlank: true,
title: 'TDesign 文档(外链)',
},
},
],
},
];

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { NotificationItem } from '@/types/interface'; import type { NotificationItem } from '@/types/interface';
const msgData = [ const msgData = [
{ {

View File

@ -19,7 +19,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) => { displayMode: (state): 'dark' | 'light' => {
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) {
@ -27,7 +27,7 @@ export const useSettingStore = defineStore('setting', {
} }
return 'light'; return 'light';
} }
return state.mode; return state.mode as 'dark' | 'light';
}, },
}, },
actions: { actions: {

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { TRouterInfo, TTabRouterType } from '@/types/interface'; import type { TRouterInfo, TTabRouterType } from '@/types/interface';
import { store } from '@/store'; import { store } from '@/store';
const homeRoute: Array<TRouterInfo> = [ const homeRoute: Array<TRouterInfo> = [

View File

@ -10,11 +10,18 @@
margin-left: var(--td-comp-margin-s); margin-left: var(--td-comp-margin-s);
} }
.t-pagination-mini {
.t-button + .t-button {
margin-left: 0;
}
}
.t-jumper { .t-jumper {
.t-button + .t-button { .t-button + .t-button {
margin-left: 0; margin-left: 0;
} }
} }
.@{starter-prefix}-link { .@{starter-prefix}-link {
color: var(--td-brand-color); color: var(--td-brand-color);
text-decoration: none; text-decoration: none;
@ -80,7 +87,7 @@
} }
&-layout { &-layout {
height: calc(100vh - 64px); height: calc(100vh - var(--td-comp-size-xxxl));
overflow-y: scroll; overflow-y: scroll;
&-tabs-nav { &-tabs-nav {
max-width: 100%; max-width: 100%;

View File

@ -4,6 +4,7 @@ import STYLE_CONFIG from '@/config/style';
export interface MenuRoute { export interface MenuRoute {
path: string; path: string;
title?: string; title?: string;
name?: string;
icon?: icon?:
| string | string
| { | {
@ -42,6 +43,7 @@ export interface TRouterInfo {
name?: RouteRecordName; name?: RouteRecordName;
isAlive?: boolean; isAlive?: boolean;
isHome?: boolean; isHome?: boolean;
meta?: any;
} }
export interface TTabRouterType { export interface TTabRouterType {

View File

@ -17,7 +17,14 @@
"@/*": ["src/*"] "@/*": ["src/*"]
} }
}, },
"include": ["**/*.ts", "src/**/*.d.ts", "include": [
"src/types/**/*.d.ts", "src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], "**/*.ts",
"src/**/*.d.ts",
"src/types/**/*.d.ts",
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"node_modules/tdesign-vue-next/global.d.ts"
],
"compileOnSave": false "compileOnSave": false
} }