feat: batch table
This commit is contained in:
parent
d8d94a0c20
commit
51ac0e2f97
|
@ -1,6 +1,6 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test } from 'vitest';
|
||||||
import Footer from '@/components/footer/index.vue';
|
import Footer from '@/components/pure/footer/index.vue';
|
||||||
|
|
||||||
describe('Footer', () => {
|
describe('Footer', () => {
|
||||||
test('renders the correct text', () => {
|
test('renders the correct text', () => {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test } from 'vitest';
|
||||||
import MsBaseTable from '@/components/ms-table/base-table.vue';
|
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import { MsTableColumn } from '@/components/ms-table/type';
|
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||||
import useTable from '@/components/ms-table/useTable';
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
import { getTableList } from '@/api/modules/api-test/index';
|
import { getTableList } from '@/api/modules/api-test/index';
|
||||||
|
|
||||||
const columns: MsTableColumn = [
|
const columns: MsTableColumn = [
|
||||||
|
@ -71,7 +71,6 @@ describe('MS-Table', () => {
|
||||||
test('init table with useTable', async () => {
|
test('init table with useTable', async () => {
|
||||||
const { propsRes, propsEvent, loadList, setProps } = useTable(getTableList, {
|
const { propsRes, propsEvent, loadList, setProps } = useTable(getTableList, {
|
||||||
columns,
|
columns,
|
||||||
pagination: { current: 1, pageSize: 1 },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const wrapper = mount(MsBaseTable, {
|
const wrapper = mount(MsBaseTable, {
|
||||||
|
@ -85,7 +84,7 @@ describe('MS-Table', () => {
|
||||||
expect(propsRes.value.data.length).toBe(2);
|
expect(propsRes.value.data.length).toBe(2);
|
||||||
expect(content).toBe('e7bd7179-d63a-43a5-1a65-218473ee69ca');
|
expect(content).toBe('e7bd7179-d63a-43a5-1a65-218473ee69ca');
|
||||||
|
|
||||||
setProps({ pagination: { current: 2, pageSize: 1 } });
|
setProps({});
|
||||||
loadList();
|
loadList();
|
||||||
|
|
||||||
await nextTick();
|
await nextTick();
|
|
@ -5,57 +5,57 @@
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
import '@vue/runtime-core'
|
import '@vue/runtime-core'
|
||||||
|
|
||||||
export {};
|
export {}
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module '@vue/runtime-core' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AAffix: typeof import('@arco-design/web-vue')['Affix'];
|
AAffix: typeof import('@arco-design/web-vue')['Affix']
|
||||||
AAlert: typeof import('@arco-design/web-vue')['Alert'];
|
AAlert: typeof import('@arco-design/web-vue')['Alert']
|
||||||
AAvatar: typeof import('@arco-design/web-vue')['Avatar'];
|
AAvatar: typeof import('@arco-design/web-vue')['Avatar']
|
||||||
ABadge: typeof import('@arco-design/web-vue')['Badge'];
|
ABadge: typeof import('@arco-design/web-vue')['Badge']
|
||||||
ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb'];
|
ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb']
|
||||||
ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem'];
|
ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem']
|
||||||
AButton: typeof import('@arco-design/web-vue')['Button'];
|
AButton: typeof import('@arco-design/web-vue')['Button']
|
||||||
ACard: typeof import('@arco-design/web-vue')['Card'];
|
ACard: typeof import('@arco-design/web-vue')['Card']
|
||||||
ACardMeta: typeof import('@arco-design/web-vue')['CardMeta'];
|
ACardMeta: typeof import('@arco-design/web-vue')['CardMeta']
|
||||||
ACol: typeof import('@arco-design/web-vue')['Col'];
|
ACol: typeof import('@arco-design/web-vue')['Col']
|
||||||
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider'];
|
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
|
||||||
ADivider: typeof import('@arco-design/web-vue')['Divider'];
|
ADivider: typeof import('@arco-design/web-vue')['Divider']
|
||||||
ADoption: typeof import('@arco-design/web-vue')['Doption'];
|
ADoption: typeof import('@arco-design/web-vue')['Doption']
|
||||||
ADrawer: typeof import('@arco-design/web-vue')['Drawer'];
|
ADrawer: typeof import('@arco-design/web-vue')['Drawer']
|
||||||
ADropdown: typeof import('@arco-design/web-vue')['Dropdown'];
|
ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
|
||||||
AInputNumber: typeof import('@arco-design/web-vue')['InputNumber'];
|
AInputNumber: typeof import('@arco-design/web-vue')['InputNumber']
|
||||||
ALayout: typeof import('@arco-design/web-vue')['Layout'];
|
ALayout: typeof import('@arco-design/web-vue')['Layout']
|
||||||
ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent'];
|
ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent']
|
||||||
ALayoutFooter: typeof import('@arco-design/web-vue')['LayoutFooter'];
|
ALayoutFooter: typeof import('@arco-design/web-vue')['LayoutFooter']
|
||||||
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider'];
|
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
|
||||||
ALink: typeof import('@arco-design/web-vue')['Link'];
|
ALink: typeof import('@arco-design/web-vue')['Link']
|
||||||
AList: typeof import('@arco-design/web-vue')['List'];
|
AList: typeof import('@arco-design/web-vue')['List']
|
||||||
AListItem: typeof import('@arco-design/web-vue')['ListItem'];
|
AListItem: typeof import('@arco-design/web-vue')['ListItem']
|
||||||
AListItemMeta: typeof import('@arco-design/web-vue')['ListItemMeta'];
|
AListItemMeta: typeof import('@arco-design/web-vue')['ListItemMeta']
|
||||||
AMenu: typeof import('@arco-design/web-vue')['Menu'];
|
AMenu: typeof import('@arco-design/web-vue')['Menu']
|
||||||
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem'];
|
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
|
||||||
AModal: typeof import('@arco-design/web-vue')['Modal'];
|
AModal: typeof import('@arco-design/web-vue')['Modal']
|
||||||
AOption: typeof import('@arco-design/web-vue')['Option'];
|
AOption: typeof import('@arco-design/web-vue')['Option']
|
||||||
APagination: typeof import('@arco-design/web-vue')['Pagination'];
|
APagination: typeof import('@arco-design/web-vue')['Pagination']
|
||||||
APopover: typeof import('@arco-design/web-vue')['Popover'];
|
APopover: typeof import('@arco-design/web-vue')['Popover']
|
||||||
AResult: typeof import('@arco-design/web-vue')['Result'];
|
AResult: typeof import('@arco-design/web-vue')['Result']
|
||||||
ARow: typeof import('@arco-design/web-vue')['Row'];
|
ARow: typeof import('@arco-design/web-vue')['Row']
|
||||||
ASelect: typeof import('@arco-design/web-vue')['Select'];
|
ASelect: typeof import('@arco-design/web-vue')['Select']
|
||||||
ASkeleton: typeof import('@arco-design/web-vue')['Skeleton'];
|
ASkeleton: typeof import('@arco-design/web-vue')['Skeleton']
|
||||||
ASkeletonLine: typeof import('@arco-design/web-vue')['SkeletonLine'];
|
ASkeletonLine: typeof import('@arco-design/web-vue')['SkeletonLine']
|
||||||
ASkeletonShape: typeof import('@arco-design/web-vue')['SkeletonShape'];
|
ASkeletonShape: typeof import('@arco-design/web-vue')['SkeletonShape']
|
||||||
ASpace: typeof import('@arco-design/web-vue')['Space'];
|
ASpace: typeof import('@arco-design/web-vue')['Space']
|
||||||
ASpin: typeof import('@arco-design/web-vue')['Spin'];
|
ASpin: typeof import('@arco-design/web-vue')['Spin']
|
||||||
ASubMenu: typeof import('@arco-design/web-vue')['SubMenu'];
|
ASubMenu: typeof import('@arco-design/web-vue')['SubMenu']
|
||||||
ASwitch: typeof import('@arco-design/web-vue')['Switch'];
|
ASwitch: typeof import('@arco-design/web-vue')['Switch']
|
||||||
ATabPane: typeof import('@arco-design/web-vue')['TabPane'];
|
ATabPane: typeof import('@arco-design/web-vue')['TabPane']
|
||||||
ATabs: typeof import('@arco-design/web-vue')['Tabs'];
|
ATabs: typeof import('@arco-design/web-vue')['Tabs']
|
||||||
ATag: typeof import('@arco-design/web-vue')['Tag'];
|
ATag: typeof import('@arco-design/web-vue')['Tag']
|
||||||
ATooltip: typeof import('@arco-design/web-vue')['Tooltip'];
|
ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
|
||||||
ATypographyParagraph: typeof import('@arco-design/web-vue')['TypographyParagraph'];
|
ATypographyParagraph: typeof import('@arco-design/web-vue')['TypographyParagraph']
|
||||||
ATypographyText: typeof import('@arco-design/web-vue')['TypographyText'];
|
ATypographyText: typeof import('@arco-design/web-vue')['TypographyText']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink'];
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView'];
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
<a-config-provider :locale="locale">
|
<a-config-provider :locale="locale">
|
||||||
<router-view />
|
<router-view />
|
||||||
<global-setting />
|
<global-setting />
|
||||||
<ThemeBox />
|
|
||||||
</a-config-provider>
|
</a-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -10,9 +9,8 @@
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import enUS from '@arco-design/web-vue/es/locale/lang/en-us';
|
import enUS from '@arco-design/web-vue/es/locale/lang/en-us';
|
||||||
import zhCN from '@arco-design/web-vue/es/locale/lang/zh-cn';
|
import zhCN from '@arco-design/web-vue/es/locale/lang/zh-cn';
|
||||||
import GlobalSetting from '@/components/global-setting/index.vue';
|
import GlobalSetting from '@/components/pure/global-setting/index.vue';
|
||||||
import useLocale from '@/locale/useLocale';
|
import useLocale from '@/locale/useLocale';
|
||||||
import ThemeBox from '@/components/theme-box/index.vue';
|
|
||||||
|
|
||||||
const { currentLocale } = useLocale();
|
const { currentLocale } = useLocale();
|
||||||
const locale = computed(() => {
|
const locale = computed(() => {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import MSR from '@/api/http/index';
|
import MSR from '@/api/http/index';
|
||||||
import { GetApiTestList, GetApiTestListUrl } from '@/api/requrls/api-test';
|
import { GetApiTestList, GetApiTestListUrl } from '@/api/requrls/api-test';
|
||||||
import { QueryParams } from '@/components/ms-table/useTable';
|
import { QueryParams } from '@/models/common';
|
||||||
import { ApiTestListI } from '@/models/api-test';
|
import { ApiTestListI } from '@/models/api-test';
|
||||||
|
|
||||||
export function getTableList(params: QueryParams) {
|
export function getTableList(params: QueryParams) {
|
||||||
const { current, pageSize } = params;
|
const { current, pageSize, sort, filter, keyword } = params;
|
||||||
return MSR.post<ApiTestListI>({
|
return MSR.post<ApiTestListI>({
|
||||||
url: GetApiTestList,
|
url: GetApiTestList,
|
||||||
data: { current, pageSize, projectId: 'test-project-id' },
|
data: { current, pageSize, sort, filter, keyword, projectId: 'test-project-id' },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="ms-base-tale">
|
|
||||||
<a-table v-bind="$attrs">
|
|
||||||
<template v-for="(item, key, i) in slots" :key="i" #[key]="{ record, rowIndex, column }">
|
|
||||||
<slot :name="key" v-bind="{ rowIndex, record, column }"></slot>
|
|
||||||
</template>
|
|
||||||
</a-table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { useSlots } from 'vue';
|
|
||||||
|
|
||||||
const slots = useSlots();
|
|
||||||
</script>
|
|
|
@ -1,3 +0,0 @@
|
||||||
export default {
|
|
||||||
msTable: {},
|
|
||||||
};
|
|
|
@ -1,3 +0,0 @@
|
||||||
export default {
|
|
||||||
msTable: {},
|
|
||||||
};
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
<template>
|
||||||
|
<div class="ms-base-tale">
|
||||||
|
<select-all
|
||||||
|
v-if="attrs.showSelectAll"
|
||||||
|
class="custom-action"
|
||||||
|
:total="selectTotal"
|
||||||
|
:current="selectCurrent"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
<a-table v-bind="$attrs" :selected-keys="props.selectedKeys" @selection-change="(e) => selectionChange(e, true)">
|
||||||
|
<template v-for="(item, key, i) in slots" :key="i" #[key]="{ record, rowIndex, column }">
|
||||||
|
<slot :name="key" v-bind="{ rowIndex, record, column }"></slot>
|
||||||
|
</template>
|
||||||
|
<template v-if="selectCurrent > 0" #footer>
|
||||||
|
<batch-action
|
||||||
|
:select-row-count="selectCurrent"
|
||||||
|
@batch-export="emit('batchExport')"
|
||||||
|
@batch-edit="emit('batchEdit')"
|
||||||
|
@batch-move="emit('batchMove')"
|
||||||
|
@batch-related="emit('batchRelated')"
|
||||||
|
@batch-delete="emit('batchDelete')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
// eslint-disable no-console
|
||||||
|
import { useSlots, useAttrs, computed, ref, onMounted, watchEffect } from 'vue';
|
||||||
|
import selectAll from './select-all.vue';
|
||||||
|
import { MsTabelProps, SelectAllEnum, MsPaginationI } from './type';
|
||||||
|
import BatchAction from './batchAction.vue';
|
||||||
|
|
||||||
|
const batchleft = ref('10px');
|
||||||
|
const props = defineProps({
|
||||||
|
selectedKeys: {
|
||||||
|
type: Array as unknown as () => (string | number)[],
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'selectedChange', value: (string | number)[]): void;
|
||||||
|
(e: 'batchExport'): void;
|
||||||
|
(e: 'batchEdit'): void;
|
||||||
|
(e: 'batchMove'): void;
|
||||||
|
(e: 'batchRelated'): void;
|
||||||
|
(e: 'batchDelete'): void;
|
||||||
|
}>();
|
||||||
|
const isSelectAll = ref(false);
|
||||||
|
// 全选按钮-当前的条数
|
||||||
|
const selectCurrent = ref(0);
|
||||||
|
|
||||||
|
const slots = useSlots();
|
||||||
|
const attrs = useAttrs();
|
||||||
|
|
||||||
|
const { data, rowKey, pagination }: Partial<MsTabelProps> = attrs;
|
||||||
|
|
||||||
|
// 全选按钮-总条数
|
||||||
|
const selectTotal = computed(() => {
|
||||||
|
if (pagination) {
|
||||||
|
const { pageSize } = pagination as MsPaginationI;
|
||||||
|
return pageSize;
|
||||||
|
}
|
||||||
|
return data ? data.length : 20;
|
||||||
|
});
|
||||||
|
// 选择行change事件
|
||||||
|
const selectionChange = (arr: (string | number)[], setCurrentSelect: boolean) => {
|
||||||
|
emit('selectedChange', arr);
|
||||||
|
if (setCurrentSelect) {
|
||||||
|
selectCurrent.value = arr.length;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 全选change事件
|
||||||
|
const handleChange = (v: string) => {
|
||||||
|
isSelectAll.value = v === SelectAllEnum.ALL;
|
||||||
|
if (v === SelectAllEnum.NONE) {
|
||||||
|
selectionChange([], true);
|
||||||
|
}
|
||||||
|
if (v === SelectAllEnum.CURRENT) {
|
||||||
|
if (data && data.length > 0) {
|
||||||
|
selectionChange(
|
||||||
|
data.map((item: any) => item[rowKey || 'id']),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (v === SelectAllEnum.ALL) {
|
||||||
|
const { total } = pagination as MsPaginationI;
|
||||||
|
if (data && data.length > 0) {
|
||||||
|
selectionChange(
|
||||||
|
data.map((item: any) => item[rowKey || 'id']),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
selectCurrent.value = total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据参数获取全选按钮的位置
|
||||||
|
const getBatchLeft = () => {
|
||||||
|
if (attrs.enableDrag) {
|
||||||
|
return '30px';
|
||||||
|
}
|
||||||
|
switch (attrs.size) {
|
||||||
|
case 'small':
|
||||||
|
return '10px';
|
||||||
|
case 'mini':
|
||||||
|
return '10px';
|
||||||
|
default:
|
||||||
|
return '10px';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
batchleft.value = getBatchLeft();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.ms-base-tale {
|
||||||
|
position: relative;
|
||||||
|
.custom-action {
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
left: v-bind(batchleft);
|
||||||
|
z-index: 100;
|
||||||
|
border-radius: 2px;
|
||||||
|
line-height: 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,31 @@
|
||||||
|
<template>
|
||||||
|
<div class="ms-table__patch-action">
|
||||||
|
<span>{{ t('msTable.batch.selected', { count: props.selectRowCount }) }}</span>
|
||||||
|
<a-button @click="emit('batchExport')">{{ t('msTable.batch.export') }}</a-button>
|
||||||
|
<a-button @click="emit('batchEdit')">{{ t('msTable.batch.edit') }}</a-button>
|
||||||
|
<a-button @click="emit('batchExport')">{{ t('msTable.batch.moveTo') }}</a-button>
|
||||||
|
<a-button @click="emit('batchExport')">{{ t('msTable.batch.copyTo') }}</a-button>
|
||||||
|
<a-button @click="emit('batchExport')">{{ t('msTable.batch.related') }}</a-button>
|
||||||
|
<a-button @click="emit('batchExport')">{{ t('msTable.batch.delete') }}</a-button>
|
||||||
|
<a-button @click="emit('clear')">{{ t('msTable.batch.clear') }}</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const props = defineProps({
|
||||||
|
selectRowCount: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'batchExport'): void;
|
||||||
|
(e: 'batchEdit'): void;
|
||||||
|
(e: 'batchMove'): void;
|
||||||
|
(e: 'batchRelated'): void;
|
||||||
|
(e: 'batchDelete'): void;
|
||||||
|
(e: 'clear'): void;
|
||||||
|
}>();
|
||||||
|
</script>
|
|
@ -0,0 +1,19 @@
|
||||||
|
export default {
|
||||||
|
msTable: {
|
||||||
|
current: 'Select Current Page',
|
||||||
|
all: 'Select All Pages',
|
||||||
|
batch: {
|
||||||
|
title: '批量操作',
|
||||||
|
selected: '已选择 {count} 项',
|
||||||
|
export: '导出',
|
||||||
|
edit: '编辑',
|
||||||
|
delete: '删除',
|
||||||
|
moveTo: '移动到',
|
||||||
|
copyTo: '复制到',
|
||||||
|
related: '关联需求',
|
||||||
|
generate: '生成依赖关系',
|
||||||
|
add: '添加到公用用例库',
|
||||||
|
clear: 'clear',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,19 @@
|
||||||
|
export default {
|
||||||
|
msTable: {
|
||||||
|
current: '全选当前页',
|
||||||
|
all: '全选所有页',
|
||||||
|
batch: {
|
||||||
|
title: '批量操作',
|
||||||
|
selected: '已选择 {count} 项',
|
||||||
|
export: '导出',
|
||||||
|
edit: '编辑',
|
||||||
|
delete: '删除',
|
||||||
|
moveTo: '移动到',
|
||||||
|
copyTo: '复制到',
|
||||||
|
related: '关联需求',
|
||||||
|
generate: '生成依赖关系',
|
||||||
|
add: '添加到公用用例库',
|
||||||
|
clear: '清空',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,75 @@
|
||||||
|
<template>
|
||||||
|
<div class="ms-table-select-all items-center text-base">
|
||||||
|
<a-checkbox v-model="checked" class="text-base" :indeterminate="indeterminate" @change="handleCheckChange" />
|
||||||
|
<a-dropdown position="bl" @select="handleSelect">
|
||||||
|
<a-icon-down class="dropdown-icon ml-0.5" />
|
||||||
|
<template #content>
|
||||||
|
<a-doption :value="SelectAllEnum.CURRENT">{{ t('msTable.current') }}</a-doption>
|
||||||
|
<a-doption :value="SelectAllEnum.ALL">{{ t('msTable.all') }}</a-doption>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watchEffect } from 'vue';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import { SelectAllEnum } from './type';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'change', value: string): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
current: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
total: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const checked = ref(false);
|
||||||
|
const indeterminate = ref(false);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (props.current === 0) {
|
||||||
|
checked.value = false;
|
||||||
|
indeterminate.value = false;
|
||||||
|
} else if (props.current < props.total) {
|
||||||
|
checked.value = false;
|
||||||
|
indeterminate.value = true;
|
||||||
|
} else if (props.current >= props.total) {
|
||||||
|
checked.value = true;
|
||||||
|
indeterminate.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSelect = (v: string | number | Record<string, any> | undefined) => {
|
||||||
|
emit('change', v as string);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckChange = () => {
|
||||||
|
if (checked.value) {
|
||||||
|
handleSelect(SelectAllEnum.CURRENT);
|
||||||
|
} else {
|
||||||
|
handleSelect(SelectAllEnum.NONE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.ms-table-select-all {
|
||||||
|
.dropdown-icon {
|
||||||
|
color: rgb(var(--primary-6));
|
||||||
|
}
|
||||||
|
.dropdown-icon:hover {
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgb(var(--primary-3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,13 +1,14 @@
|
||||||
import { TableColumnData, TableData, TableDraggable } from '@arco-design/web-vue';
|
import { TableColumnData, TableData, TableDraggable, TableChangeExtra } from '@arco-design/web-vue';
|
||||||
|
|
||||||
export interface MsPaginationI {
|
export interface MsPaginationI {
|
||||||
pageSize?: number;
|
current: number;
|
||||||
total?: number;
|
pageSize: number;
|
||||||
current?: number;
|
total: number;
|
||||||
|
showPageSize: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格属性
|
// 表格属性
|
||||||
export interface MsTabelProps extends MsPaginationI {
|
export interface MsTabelProps {
|
||||||
// 表格列 - 详见 TableColumn https://arco.design/web-vue/components/table-column;
|
// 表格列 - 详见 TableColumn https://arco.design/web-vue/components/table-column;
|
||||||
columns: TableColumnData[];
|
columns: TableColumnData[];
|
||||||
// 表格数据 - 详见 TableData https://arco.design/web-vue/components/table-data;
|
// 表格数据 - 详见 TableData https://arco.design/web-vue/components/table-data;
|
||||||
|
@ -20,6 +21,7 @@ export interface MsTabelProps extends MsPaginationI {
|
||||||
y?: number | string;
|
y?: number | string;
|
||||||
};
|
};
|
||||||
// 表格是否可拖拽
|
// 表格是否可拖拽
|
||||||
|
enableDrag?: boolean;
|
||||||
draggable?: TableDraggable;
|
draggable?: TableDraggable;
|
||||||
// 表格是否可编辑
|
// 表格是否可编辑
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
|
@ -29,6 +31,8 @@ export interface MsTabelProps extends MsPaginationI {
|
||||||
sortable?: boolean;
|
sortable?: boolean;
|
||||||
// 表格是否可选中
|
// 表格是否可选中
|
||||||
selectable?: boolean;
|
selectable?: boolean;
|
||||||
|
// 展示自定义全选
|
||||||
|
showSelectAll?: boolean;
|
||||||
// 表格是否可展开
|
// 表格是否可展开
|
||||||
expandable?: boolean;
|
expandable?: boolean;
|
||||||
// 表格是否可固定表头
|
// 表格是否可固定表头
|
||||||
|
@ -41,9 +45,28 @@ export interface MsTabelProps extends MsPaginationI {
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
// pagination
|
// pagination
|
||||||
pagination?: MsPaginationI;
|
pagination: MsPaginationI | boolean;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MsTableSelectAll {
|
||||||
|
value: boolean;
|
||||||
|
total: number;
|
||||||
|
current: number;
|
||||||
|
type: 'all' | 'page';
|
||||||
|
}
|
||||||
|
|
||||||
export type MsTableData = TableData[];
|
export type MsTableData = TableData[];
|
||||||
export type MsTableColumn = TableColumnData[];
|
export type MsTableColumn = TableColumnData[];
|
||||||
|
export type MSTableChangeExtra = TableChangeExtra;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-shadow
|
||||||
|
export enum SelectAllEnum {
|
||||||
|
ALL = 'all',
|
||||||
|
CURRENT = 'current',
|
||||||
|
NONE = 'none',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SortItem {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
// 核心的封装方法,详细参数看文档 https://arco.design/vue/component/table
|
// 核心的封装方法,详细参数看文档 https://arco.design/vue/component/table
|
||||||
// hook/table-props.ts
|
// hook/table-props.ts
|
||||||
|
|
||||||
import { ref, reactive } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { MsTabelProps, MsTableData, MsTableColumn } from './type';
|
import { MsTabelProps, MsTableData, MsTableColumn } from './type';
|
||||||
import { ApiTestListI } from '@/models/api-test';
|
import { ApiTestListI } from '@/models/api-test';
|
||||||
|
import { TableData } from '@arco-design/web-vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { QueryParams } from '@/models/common';
|
||||||
|
|
||||||
export interface Pagination {
|
export interface Pagination {
|
||||||
current: number;
|
current: number;
|
||||||
|
@ -12,47 +15,62 @@ export interface Pagination {
|
||||||
showPageSize: boolean;
|
showPageSize: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryParams {
|
|
||||||
current: number;
|
|
||||||
pageSize: number;
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetListFunc = (v: QueryParams) => Promise<ApiTestListI>;
|
type GetListFunc = (v: QueryParams) => Promise<ApiTestListI>;
|
||||||
export default function useTbleProps(loadListFunc: GetListFunc, props?: Partial<MsTabelProps>) {
|
export default function useTbleProps(loadListFunc: GetListFunc, props?: Partial<MsTabelProps>) {
|
||||||
// 行选择
|
// 行选择
|
||||||
const rowSelection = reactive({
|
const rowSelection = {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
showCheckedAll: true,
|
showCheckedAll: false,
|
||||||
onlyCurrent: false,
|
};
|
||||||
});
|
|
||||||
|
|
||||||
const defaultProps: MsTabelProps = {
|
const defaultProps: MsTabelProps = {
|
||||||
'bordered': true,
|
bordered: true,
|
||||||
'size': 'small',
|
showPagination: true,
|
||||||
'scroll': { y: '550px', x: '1400px' },
|
size: 'small',
|
||||||
'checkable': true,
|
scroll: { y: '550px', x: '1400px' },
|
||||||
'expandable': false,
|
checkable: true,
|
||||||
'loading': true,
|
loading: true,
|
||||||
'data': [] as MsTableData,
|
data: [] as MsTableData,
|
||||||
'columns': [] as MsTableColumn,
|
columns: [] as MsTableColumn,
|
||||||
'pagination': {
|
pagination: {
|
||||||
current: 1,
|
current: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
total: 0,
|
total: 0,
|
||||||
showPageSize: true,
|
showPageSize: true,
|
||||||
} as Pagination,
|
} as Pagination,
|
||||||
'draggable': { type: 'handle' },
|
rowKey: 'id',
|
||||||
'row-key': 'id',
|
selectedKeys: [],
|
||||||
|
selectedAll: false,
|
||||||
|
enableDrag: false,
|
||||||
|
showSelectAll: true,
|
||||||
...props,
|
...props,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 属性组
|
// 属性组
|
||||||
const propsRes = ref(defaultProps);
|
const propsRes = ref(defaultProps);
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
const sortItem = ref<object>({});
|
||||||
|
|
||||||
|
// 筛选
|
||||||
|
const filterItem = ref<object>({});
|
||||||
|
|
||||||
|
// keyword
|
||||||
|
const keyword = ref('');
|
||||||
|
|
||||||
|
// 是否分页
|
||||||
|
if (!propsRes.value.showPagination) {
|
||||||
|
propsRes.value.pagination = false;
|
||||||
|
}
|
||||||
|
|
||||||
// 是否可选中
|
// 是否可选中
|
||||||
if (propsRes.value.selectable) {
|
if (propsRes.value.selectable) {
|
||||||
propsRes.value['row-selection'] = rowSelection;
|
propsRes.value.rowSelection = rowSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否可拖拽
|
||||||
|
if (propsRes.value.enableDrag) {
|
||||||
|
propsRes.value.draggable = { type: 'handle' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载效果
|
// 加载效果
|
||||||
|
@ -72,9 +90,11 @@ export default function useTbleProps(loadListFunc: GetListFunc, props?: Partial<
|
||||||
}
|
}
|
||||||
|
|
||||||
const setPagination = ({ current, total }: SetPaginationPrams) => {
|
const setPagination = ({ current, total }: SetPaginationPrams) => {
|
||||||
if (propsRes.value.pagination) {
|
if (propsRes.value.pagination && typeof propsRes.value.pagination === 'object') {
|
||||||
propsRes.value.pagination.current = current;
|
propsRes.value.pagination.current = current;
|
||||||
if (total) propsRes.value.pagination.total = total;
|
if (total) {
|
||||||
|
propsRes.value.pagination.total = total;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -93,6 +113,10 @@ export default function useTbleProps(loadListFunc: GetListFunc, props?: Partial<
|
||||||
loadListParams.value = params || {};
|
loadListParams.value = params || {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setKeyword = (v: string) => {
|
||||||
|
keyword.value = v;
|
||||||
|
};
|
||||||
|
|
||||||
// 加载分页列表数据
|
// 加载分页列表数据
|
||||||
const loadList = async () => {
|
const loadList = async () => {
|
||||||
const { current, pageSize } = propsRes.value.pagination as Pagination;
|
const { current, pageSize } = propsRes.value.pagination as Pagination;
|
||||||
|
@ -100,9 +124,20 @@ export default function useTbleProps(loadListFunc: GetListFunc, props?: Partial<
|
||||||
const data = await loadListFunc({
|
const data = await loadListFunc({
|
||||||
current,
|
current,
|
||||||
pageSize,
|
pageSize,
|
||||||
...loadListParams.value,
|
sort: sortItem.value,
|
||||||
|
filter: filterItem.value,
|
||||||
|
keyword: keyword.value,
|
||||||
|
});
|
||||||
|
const tmpArr = data.list as unknown as MsTableData;
|
||||||
|
propsRes.value.data = tmpArr.map((item: TableData) => {
|
||||||
|
if (item.updateTime) {
|
||||||
|
item.updateTime = dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
}
|
||||||
|
if (item.createTime) {
|
||||||
|
item.createTime = dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
}
|
||||||
|
return item;
|
||||||
});
|
});
|
||||||
propsRes.value.data = data.list as unknown as MsTableData;
|
|
||||||
setPagination({ current: data.current, total: data.total });
|
setPagination({ current: data.current, total: data.total });
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return data;
|
return data;
|
||||||
|
@ -113,7 +148,14 @@ export default function useTbleProps(loadListFunc: GetListFunc, props?: Partial<
|
||||||
// 排序触发
|
// 排序触发
|
||||||
sorterChange: (dataIndex: string, direction: string) => {
|
sorterChange: (dataIndex: string, direction: string) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(dataIndex, direction);
|
sortItem.value = { [dataIndex]: direction };
|
||||||
|
loadList();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 筛选触发
|
||||||
|
filterChange: (dataIndex: string, filteredValues: string[]) => {
|
||||||
|
filterItem.value = { [dataIndex]: filteredValues };
|
||||||
|
loadList();
|
||||||
},
|
},
|
||||||
// 分页触发
|
// 分页触发
|
||||||
pageChange: (current: number) => {
|
pageChange: (current: number) => {
|
||||||
|
@ -122,13 +164,23 @@ export default function useTbleProps(loadListFunc: GetListFunc, props?: Partial<
|
||||||
},
|
},
|
||||||
// 修改每页显示条数
|
// 修改每页显示条数
|
||||||
pageSizeChange: (pageSize: number) => {
|
pageSizeChange: (pageSize: number) => {
|
||||||
if (propsRes.value.pagination) {
|
if (propsRes.value.pagination && typeof propsRes.value.pagination === 'object') {
|
||||||
propsRes.value.pagination.pageSize = pageSize;
|
propsRes.value.pagination.pageSize = pageSize;
|
||||||
}
|
}
|
||||||
loadList();
|
loadList();
|
||||||
},
|
},
|
||||||
|
// 选择触发
|
||||||
|
selectedChange: (arr: (string | number)[]) => {
|
||||||
|
if (arr.length === 0) {
|
||||||
|
propsRes.value.pagination = defaultProps.pagination;
|
||||||
|
} else {
|
||||||
|
propsRes.value.pagination = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
propsRes.value.selectedKeys = arr;
|
||||||
|
},
|
||||||
change: (_data: MsTableData) => {
|
change: (_data: MsTableData) => {
|
||||||
if (propsRes.value.draggable) {
|
if (propsRes.value.draggable && _data instanceof Array) {
|
||||||
// eslint-disable-next-line vue/require-explicit-emits
|
// eslint-disable-next-line vue/require-explicit-emits
|
||||||
propsRes.value.data = _data;
|
propsRes.value.data = _data;
|
||||||
}
|
}
|
||||||
|
@ -143,5 +195,6 @@ export default function useTbleProps(loadListFunc: GetListFunc, props?: Partial<
|
||||||
loadList,
|
loadList,
|
||||||
setPagination,
|
setPagination,
|
||||||
setLoadListParams,
|
setLoadListParams,
|
||||||
|
setKeyword,
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -142,9 +142,9 @@
|
||||||
import { LOCALE_OPTIONS } from '@/locale';
|
import { LOCALE_OPTIONS } from '@/locale';
|
||||||
import useLocale from '@/locale/useLocale';
|
import useLocale from '@/locale/useLocale';
|
||||||
import useUser from '@/hooks/useUser';
|
import useUser from '@/hooks/useUser';
|
||||||
import Menu from '@/components/menu/index.vue';
|
import Menu from '@/components/pure/menu/index.vue';
|
||||||
import MessageBox from '../message-box/index.vue';
|
import MessageBox from '../message-box/index.vue';
|
||||||
import ProjcetSelection from '@/components/project-selection/index.vue';
|
import ProjcetSelection from '@/components/pure/project-selection/index.vue';
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
|
@ -1,275 +0,0 @@
|
||||||
<template>
|
|
||||||
<a-badge class="theme-badge bottom-[124px] right-[70px]" :count="theme ? 1 : 0" dot>
|
|
||||||
<a-button
|
|
||||||
class="theme-badge-button"
|
|
||||||
:shape="hover ? 'round' : 'circle'"
|
|
||||||
size="large"
|
|
||||||
@click="modalVisible = true"
|
|
||||||
@mouseenter="hover = true"
|
|
||||||
@mouseleave="hover = false"
|
|
||||||
>
|
|
||||||
<IconSkin />
|
|
||||||
<span v-if="hover" style="margin-left: 8px">
|
|
||||||
{{ t('themeBox.installTheme') }}
|
|
||||||
</span>
|
|
||||||
</a-button>
|
|
||||||
</a-badge>
|
|
||||||
<a-modal :visible="modalVisible" :width="900" @ok="modalVisible = false" @cancel="modalVisible = false">
|
|
||||||
<template #title>
|
|
||||||
<div class="theme-box-header pr-[24px]">
|
|
||||||
<span>{{ t('themeBox.installTheme') }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<a-row :gutter="[20, 20]">
|
|
||||||
<template v-if="isLoading">
|
|
||||||
<a-col v-for="(_, index) of loadingFillArray" :key="index" :span="8">
|
|
||||||
<a-card v-if="isLoading" class="theme-box-card">
|
|
||||||
<template #cover>
|
|
||||||
<a-skeleton animation>
|
|
||||||
<a-skeleton-shape style="width: 272px; height: 160px" />
|
|
||||||
</a-skeleton>
|
|
||||||
</template>
|
|
||||||
<a-card-meta>
|
|
||||||
<template #title>
|
|
||||||
<a-skeleton animation>
|
|
||||||
<a-skeleton-line :line-height="25" />
|
|
||||||
</a-skeleton>
|
|
||||||
</template>
|
|
||||||
</a-card-meta>
|
|
||||||
<a-skeleton animation>
|
|
||||||
<a-skeleton-shape style="margin-top: 20px; margin-left: auto; width: 100px; height: 24px" />
|
|
||||||
</a-skeleton>
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="themeList.length > 0">
|
|
||||||
<a-col v-for="item of themeList" :key="item.themeId" :span="8">
|
|
||||||
<a-card class="theme-box-card">
|
|
||||||
<template #cover>
|
|
||||||
<img :src="item.cover" style="height: 160px" alt="theme-cover" />
|
|
||||||
</template>
|
|
||||||
<template #actions>
|
|
||||||
<a-button
|
|
||||||
class="theme-box-card-link"
|
|
||||||
type="text"
|
|
||||||
size="mini"
|
|
||||||
:href="`https://arco.design/themes/design/${item.themeId}`"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<IconLink />
|
|
||||||
</template>
|
|
||||||
{{ t('themeBox.openInDesignLab') }}
|
|
||||||
</a-button>
|
|
||||||
<a-tag v-if="theme && theme.themeId === item.themeId" color="arcoblue">
|
|
||||||
{{ t('themeBox.currentTheme') }}
|
|
||||||
</a-tag>
|
|
||||||
<a-button v-else type="primary" size="mini" @click="() => useTheme(item)">
|
|
||||||
{{ t('themeBox.install') }}
|
|
||||||
</a-button>
|
|
||||||
</template>
|
|
||||||
<a-card-meta :title="item.themeName" />
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<Empty style="margin: 200px 0">
|
|
||||||
<template #description>
|
|
||||||
{{ t('themeBox.noResult') }}
|
|
||||||
<a-link :href="`https://arco.design/themes`">
|
|
||||||
{{ t('themeBox.createTheme') }}
|
|
||||||
</a-link>
|
|
||||||
</template>
|
|
||||||
</Empty>
|
|
||||||
</template>
|
|
||||||
</a-row>
|
|
||||||
<div class="theme-box-bottom mt-[20px]">
|
|
||||||
<a-pagination :total="total" :current="page" :page-size="6" @change="onPageChange" />
|
|
||||||
</div>
|
|
||||||
<template v-if="theme" #footer>
|
|
||||||
<div class="theme-box-footer">
|
|
||||||
<a-typography-text bold> {{ t('themeBox.currentTheme') }}: {{ theme.themeName }} </a-typography-text>
|
|
||||||
<a-button type="primary" status="danger" size="small" @click="onResetClick">
|
|
||||||
{{ t('themeBox.resetTheme') }}
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</a-modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, onMounted, ref, watch } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { Notification } from '@arco-design/web-vue';
|
|
||||||
import axios from 'axios';
|
|
||||||
import { IconSkin, IconLink } from '@arco-design/web-vue/es/icon';
|
|
||||||
import { getLocalStorage, removeLocalStorage, setLocalStorage } from '../../utils/local-storage';
|
|
||||||
import { apiBasename } from '../../utils/api';
|
|
||||||
import { ThemeData } from './interface';
|
|
||||||
|
|
||||||
const THEME_LINK_ID = 'arco-custom-theme';
|
|
||||||
const loadingFillArray = Array(3).fill(1);
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'ThemeBox',
|
|
||||||
components: {
|
|
||||||
IconSkin,
|
|
||||||
IconLink,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const { t } = useI18n();
|
|
||||||
const theme = ref<ThemeData>();
|
|
||||||
const themeList = ref<ThemeData[]>([]);
|
|
||||||
const total = ref(0);
|
|
||||||
const page = ref(1);
|
|
||||||
const modalVisible = ref(false);
|
|
||||||
const searchValue = ref('');
|
|
||||||
const isLoading = ref(false);
|
|
||||||
const hover = ref(false);
|
|
||||||
|
|
||||||
const removeTheme = () => {
|
|
||||||
const linkElement = document.getElementById(THEME_LINK_ID);
|
|
||||||
if (linkElement) {
|
|
||||||
document.body.removeChild(linkElement);
|
|
||||||
}
|
|
||||||
theme.value = undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const addTheme = (_theme: ThemeData, notice: boolean) => {
|
|
||||||
const url = `${_theme.unpkgHost}${_theme.packageName}/css/arco.css`;
|
|
||||||
axios
|
|
||||||
.get(url)
|
|
||||||
.then(() => {
|
|
||||||
if (theme.value) {
|
|
||||||
removeTheme();
|
|
||||||
}
|
|
||||||
const linkElement = document.createElement('link');
|
|
||||||
linkElement.id = THEME_LINK_ID;
|
|
||||||
linkElement.href = url;
|
|
||||||
linkElement.type = 'text/css';
|
|
||||||
linkElement.rel = 'stylesheet';
|
|
||||||
document.body.appendChild(linkElement);
|
|
||||||
theme.value = _theme;
|
|
||||||
|
|
||||||
if (notice) {
|
|
||||||
Notification.success({
|
|
||||||
id: 'theme',
|
|
||||||
title: t('themeBox.installTheme'),
|
|
||||||
content: t('themeBox.installThemeSuccess'),
|
|
||||||
duration: 2000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
Notification.error({
|
|
||||||
id: 'theme',
|
|
||||||
title: t('themeBox.installTheme'),
|
|
||||||
content: t('themeBox.installThemeError'),
|
|
||||||
duration: 2000,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const useTheme = (_theme: ThemeData, notice = true) => {
|
|
||||||
addTheme(_theme, notice);
|
|
||||||
setLocalStorage('vue-custom-theme', _theme);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// const _theme = getLocalStorage<ThemeData>('vue-custom-theme', true) as ThemeData;
|
|
||||||
// if (_theme) {
|
|
||||||
// useTheme(_theme, false);
|
|
||||||
// }
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchThemeList = async (current: number, search: string) => {
|
|
||||||
isLoading.value = true;
|
|
||||||
try {
|
|
||||||
const data = await axios.get(
|
|
||||||
`${apiBasename}/themes/api/open/themes/list?pageSize=6¤tPage=${current}&depLibrary=@arco-design/web-vue&keyword=vue-ms-theme-${search}`
|
|
||||||
);
|
|
||||||
|
|
||||||
themeList.value = data.data.list;
|
|
||||||
total.value = data.data.total;
|
|
||||||
} catch (e) {
|
|
||||||
themeList.value = [];
|
|
||||||
total.value = 0;
|
|
||||||
}
|
|
||||||
isLoading.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(modalVisible, (visible) => {
|
|
||||||
if (visible) {
|
|
||||||
fetchThemeList(page.value, searchValue.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const onResetClick = () => {
|
|
||||||
removeTheme();
|
|
||||||
removeLocalStorage('vue-custom-theme');
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSearchInput = (value: string) => {
|
|
||||||
searchValue.value = value;
|
|
||||||
page.value = 1;
|
|
||||||
fetchThemeList(1, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onPageChange = (_page: number) => {
|
|
||||||
page.value = _page;
|
|
||||||
fetchThemeList(_page, searchValue.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
hover,
|
|
||||||
modalVisible,
|
|
||||||
themeList,
|
|
||||||
theme,
|
|
||||||
total,
|
|
||||||
page,
|
|
||||||
isLoading,
|
|
||||||
loadingFillArray,
|
|
||||||
searchValue,
|
|
||||||
useTheme,
|
|
||||||
onSearchInput,
|
|
||||||
onPageChange,
|
|
||||||
onResetClick,
|
|
||||||
t,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less">
|
|
||||||
.theme-box {
|
|
||||||
&-header {
|
|
||||||
@apply flex w-full items-center justify-between;
|
|
||||||
}
|
|
||||||
&-card {
|
|
||||||
&-link {
|
|
||||||
@apply opacity-0;
|
|
||||||
|
|
||||||
transition: opacity 100ms;
|
|
||||||
}
|
|
||||||
&:hover &-link {
|
|
||||||
@apply opacity-100;
|
|
||||||
}
|
|
||||||
:deep(.arco-card-meta-title) {
|
|
||||||
line-height: 25px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&-bottom {
|
|
||||||
@apply flex justify-end;
|
|
||||||
}
|
|
||||||
&-footer {
|
|
||||||
@apply flex items-center justify-between;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.theme-badge {
|
|
||||||
@apply fixed;
|
|
||||||
&-button {
|
|
||||||
border: 1px solid var(--color-fill-3) !important;
|
|
||||||
background: var(--color-bg-5) !important;
|
|
||||||
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,7 +0,0 @@
|
||||||
export interface ThemeData {
|
|
||||||
themeId: number;
|
|
||||||
themeName: string;
|
|
||||||
cover: string;
|
|
||||||
packageName: string;
|
|
||||||
unpkgHost: string;
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
export default {
|
|
||||||
themeBox: {
|
|
||||||
currentTheme: 'Current theme',
|
|
||||||
autoUseTheme: 'Automatically use theme',
|
|
||||||
install: 'Install',
|
|
||||||
installTheme: 'Install theme',
|
|
||||||
search: 'Search',
|
|
||||||
installingTheme: 'Installing theme...',
|
|
||||||
installThemeSuccess: 'Install theme successfully! ',
|
|
||||||
installThemeError: 'Install theme failed, please try again! ',
|
|
||||||
resetTheme: 'Reset theme',
|
|
||||||
resetThemeSuccess: 'Reset theme successfully! ',
|
|
||||||
openInDesignLab: 'Open in the Design Lab',
|
|
||||||
noResult: 'No related themes',
|
|
||||||
createTheme: 'Go to the Design Lab to create',
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,17 +0,0 @@
|
||||||
export default {
|
|
||||||
themeBox: {
|
|
||||||
currentTheme: '当前主题',
|
|
||||||
autoUseTheme: '自动应用主题',
|
|
||||||
install: '安装',
|
|
||||||
installTheme: '安装主题',
|
|
||||||
search: '搜索',
|
|
||||||
installingTheme: '正在安装主题...',
|
|
||||||
installThemeSuccess: '主题安装成功!',
|
|
||||||
installThemeError: '主题安装失败,请重试!',
|
|
||||||
resetTheme: '重置主题',
|
|
||||||
resetThemeSuccess: '重置主题成功!',
|
|
||||||
openInDesignLab: '在主题商店打开',
|
|
||||||
noResult: '没有相关主题',
|
|
||||||
createTheme: '前往主题商店创建',
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -48,10 +48,10 @@
|
||||||
import { ref, computed, watch, provide, onMounted } from 'vue';
|
import { ref, computed, watch, provide, onMounted } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { useAppStore, useUserStore } from '@/store';
|
import { useAppStore, useUserStore } from '@/store';
|
||||||
import NavBar from '@/components/navbar/index.vue';
|
import NavBar from '@/components/pure/navbar/index.vue';
|
||||||
import Menu from '@/components/menu/index.vue';
|
import Menu from '@/components/pure/menu/index.vue';
|
||||||
import Footer from '@/components/footer/index.vue';
|
import Footer from '@/components/pure/footer/index.vue';
|
||||||
import TabBar from '@/components/tab-bar/index.vue';
|
import TabBar from '@/components/pure/tab-bar/index.vue';
|
||||||
import usePermission from '@/hooks/usePermission';
|
import usePermission from '@/hooks/usePermission';
|
||||||
import useResponsive from '@/hooks/useResponsive';
|
import useResponsive from '@/hooks/useResponsive';
|
||||||
import PageLayout from './page-layout.vue';
|
import PageLayout from './page-layout.vue';
|
||||||
|
|
|
@ -1,38 +1,22 @@
|
||||||
import dayjsLocale from 'dayjs/locale/en';
|
import dayjsLocale from 'dayjs/locale/en';
|
||||||
import localeSettings from './settings';
|
import localeSettings from './settings';
|
||||||
import sys from './sys';
|
import sys from './sys';
|
||||||
import localeMessageBox from '@/components/message-box/locale/en-US';
|
import localeMessageBox from '@/components/pure/message-box/locale/en-US';
|
||||||
import minder from '@/components/minder-editor/locale/en-US';
|
import minder from '@/components/pure/minder-editor/locale/en-US';
|
||||||
import localeLogin from '@/views/login/locale/en-US';
|
import localeLogin from '@/views/login/locale/en-US';
|
||||||
import localeWorkplace from '@/views/dashboard/workplace/locale/en-US';
|
import localeTable from '@/components/pure/ms-table/locale/en-US';
|
||||||
import localeThemebox from '@/components/theme-box/locale/en-US';
|
|
||||||
import localeApiTest from '@/views/api-test/locale/en-US';
|
import localeApiTest from '@/views/api-test/locale/en-US';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
message: {
|
message: {
|
||||||
'menu.component': 'component hub',
|
'menu.apiTest': 'Api Test',
|
||||||
'menu.component.demo': 'component demo',
|
|
||||||
'menu.apitest': 'Api Test',
|
|
||||||
'menu.dashboard': 'Dashboard',
|
|
||||||
'menu.minder': 'Minder',
|
|
||||||
'menu.server.dashboard': 'Dashboard-Server',
|
|
||||||
'menu.server.workplace': 'Workplace-Server',
|
|
||||||
'menu.server.monitor': 'Monitor-Server',
|
|
||||||
'menu.list': 'List',
|
|
||||||
'menu.result': 'Result',
|
|
||||||
'menu.exception': 'Exception',
|
|
||||||
'menu.form': 'Form',
|
|
||||||
'menu.profile': 'Profile',
|
|
||||||
'menu.visualization': 'Data Visualization',
|
|
||||||
'menu.user': 'User Center',
|
|
||||||
'navbar.action.locale': 'Switch to English',
|
'navbar.action.locale': 'Switch to English',
|
||||||
...sys,
|
...sys,
|
||||||
...localeSettings,
|
...localeSettings,
|
||||||
...localeMessageBox,
|
...localeMessageBox,
|
||||||
...localeLogin,
|
...localeLogin,
|
||||||
...localeWorkplace,
|
|
||||||
...minder,
|
...minder,
|
||||||
...localeThemebox,
|
...localeTable,
|
||||||
...localeApiTest,
|
...localeApiTest,
|
||||||
},
|
},
|
||||||
dayjsLocale,
|
dayjsLocale,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue