Merge remote-tracking branch 'vben/main' into Gf-Vben-Admin

# Conflicts:
#	yarn.lock
This commit is contained in:
JinMao 2021-08-27 11:12:38 +08:00
commit 0c2c23bb00
20 changed files with 296 additions and 35 deletions

View File

@ -4,6 +4,7 @@ import vueJsx from '@vitejs/plugin-vue-jsx';
import legacy from '@vitejs/plugin-legacy'; import legacy from '@vitejs/plugin-legacy';
import purgeIcons from 'vite-plugin-purge-icons'; import purgeIcons from 'vite-plugin-purge-icons';
import windiCSS from 'vite-plugin-windicss'; import windiCSS from 'vite-plugin-windicss';
import vueSetupExtend from 'vite-plugin-vue-setup-extend';
import { configHtmlPlugin } from './html'; import { configHtmlPlugin } from './html';
import { configPwaConfig } from './pwa'; import { configPwaConfig } from './pwa';
import { configMockPlugin } from './mock'; import { configMockPlugin } from './mock';
@ -29,6 +30,8 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
vue(), vue(),
// have to // have to
vueJsx(), vueJsx(),
// support name
vueSetupExtend(),
]; ];
// vite-plugin-windicss // vite-plugin-windicss

View File

@ -66,7 +66,7 @@ export function configThemePlugin(isBuild: boolean): Plugin[] {
'border-color-base': '#303030', 'border-color-base': '#303030',
// 'border-color-split': '#30363d', // 'border-color-split': '#30363d',
'item-active-bg': '#111b26', 'item-active-bg': '#111b26',
'app-content-background': 'rgb(255 255 255 / 4%)', 'app-content-background': '#1e1e1e',
'tree-node-selected-bg': '#11263c', 'tree-node-selected-bg': '#11263c',
'alert-success-border-color': '#274916', 'alert-success-border-color': '#274916',

View File

@ -58,10 +58,10 @@
"vditor": "^3.8.6", "vditor": "^3.8.6",
"vue": "3.2.4", "vue": "3.2.4",
"vue-i18n": "9.1.7", "vue-i18n": "9.1.7",
"vue-json-pretty": "1.8.1",
"vue-router": "^4.0.11", "vue-router": "^4.0.11",
"vue-types": "^4.0.3", "vue-types": "^4.0.3",
"xlsx": "^0.17.1", "xlsx": "^0.17.1"
"vue-json-pretty": "1.8.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^13.1.0", "@commitlint/cli": "^13.1.0",
@ -102,7 +102,7 @@
"esno": "^0.9.1", "esno": "^0.9.1",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"http-server": "^13.0.1", "http-server": "^13.0.1",
"husky": "^7.0.1", "husky": "^7.0.2",
"inquirer": "^8.1.2", "inquirer": "^8.1.2",
"is-ci": "^3.0.0", "is-ci": "^3.0.0",
"jest": "^27.0.6", "jest": "^27.0.6",
@ -121,17 +121,18 @@
"ts-jest": "^27.0.5", "ts-jest": "^27.0.5",
"ts-node": "^10.2.1", "ts-node": "^10.2.1",
"typescript": "4.3.5", "typescript": "4.3.5",
"vite": "2.5.0", "vite": "2.5.1",
"vite-plugin-compression": "^0.3.5", "vite-plugin-compression": "^0.3.5",
"vite-plugin-html": "^2.1.0", "vite-plugin-html": "^2.1.0",
"vite-plugin-imagemin": "^0.4.5", "vite-plugin-imagemin": "^0.4.5",
"vite-plugin-mock": "^2.9.6", "vite-plugin-mock": "^2.9.6",
"vite-plugin-purge-icons": "^0.7.0", "vite-plugin-purge-icons": "^0.7.0",
"vite-plugin-pwa": "^0.11.0", "vite-plugin-pwa": "^0.11.2",
"vite-plugin-style-import": "^1.2.1", "vite-plugin-style-import": "^1.2.1",
"vite-plugin-svg-icons": "^1.0.4", "vite-plugin-svg-icons": "^1.0.4",
"vite-plugin-theme": "^0.8.1", "vite-plugin-theme": "^0.8.1",
"vite-plugin-windicss": "^1.2.8", "vite-plugin-vue-setup-extend": "^0.1.0",
"vite-plugin-windicss": "^1.3.0",
"vue-eslint-parser": "^7.10.0", "vue-eslint-parser": "^7.10.0",
"vue-tsc": "^0.3.0" "vue-tsc": "^0.3.0"
}, },

View File

@ -0,0 +1,4 @@
import { withInstall } from '/@/utils';
import cardList from './src/CardList.vue';
export const CardList = withInstall(cardList);

View File

@ -0,0 +1,178 @@
<template>
<div class="p-2">
<div class="bg-white mb-2 p-4">
<BasicForm @register="registerForm" />
</div>
{{ sliderProp.width }}
<div class="bg-white p-2">
<List
:grid="{ gutter: 5, xs: 1, sm: 2, md: 4, lg: 4, xl: 6, xxl: grid }"
:data-source="data"
:pagination="paginationProp"
>
<template #header>
<div class="flex justify-end space-x-2"
><slot name="header"></slot>
<Tooltip>
<template #title>
<div class="w-50">每行显示数量</div
><Slider
id="slider"
v-bind="sliderProp"
v-model:value="grid"
@change="sliderChange"
/></template>
<Button><TableOutlined /></Button>
</Tooltip>
<Tooltip @click="fetch">
<template #title>刷新</template>
<Button><RedoOutlined /></Button>
</Tooltip>
</div>
</template>
<template #renderItem="{ item }">
<ListItem>
<Card>
<template #title></template>
<template #cover>
<div :class="height">
<Image :src="item.imgs[0]" />
</div>
</template>
<template class="ant-card-actions" #actions>
<!-- <SettingOutlined key="setting" />-->
<EditOutlined key="edit" />
<Dropdown
:trigger="['hover']"
:dropMenuList="[
{
text: '删除',
event: '1',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, item.id),
},
},
]"
popconfirm
>
<EllipsisOutlined key="ellipsis" />
</Dropdown>
</template>
<CardMeta>
<template #title>
<TypographyText :content="item.name" :ellipsis="{ tooltip: item.address }" />
</template>
<template #avatar>
<Avatar :src="item.avatar" />
</template>
<template #description>{{ item.time }}</template>
</CardMeta>
</Card>
</ListItem>
</template>
</List>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import {
EditOutlined,
EllipsisOutlined,
RedoOutlined,
TableOutlined,
} from '@ant-design/icons-vue';
import { List, Card, Image, Typography, Tooltip, Slider, Avatar } from 'ant-design-vue';
import { Dropdown } from '/@/components/Dropdown';
import { BasicForm, useForm } from '/@/components/Form';
import { propTypes } from '/@/utils/propTypes';
import { Button } from '/@/components/Button';
import { isFunction } from '/@/utils/is';
import { useSlider, grid } from './data';
const ListItem = List.Item;
const CardMeta = Card.Meta;
const TypographyText = Typography.Text;
// slider
const sliderProp = computed(() => useSlider(4));
//
const props = defineProps({
// API
params: propTypes.object.def({}),
//api
api: propTypes.func,
});
//
const emit = defineEmits(['getMethod', 'delete']);
//
const data = ref([]);
//
// cover
//pageSize
const height = computed(() => {
return `h-${120 - grid.value * 6}`;
});
//
const [registerForm, { validate }] = useForm({
schemas: [{ field: 'type', component: 'Input', label: '类型' }],
labelWidth: 80,
baseColProps: { span: 6 },
actionColOptions: { span: 24 },
autoSubmitOnEnter: true,
submitFunc: handleSubmit,
});
//
async function handleSubmit() {
const data = await validate();
await fetch(data);
}
function sliderChange(n) {
pageSize.value = n * 4;
fetch();
}
//
onMounted(() => {
fetch();
emit('getMethod', fetch);
});
async function fetch(p = {}) {
const { api, params } = props;
if (api && isFunction(api)) {
const res = await api({ ...params, page: page.value, pageSize: pageSize.value, ...p });
data.value = res.items;
total.value = res.total;
}
}
//
const page = ref(1);
const pageSize = ref(36);
const total = ref(0);
const paginationProp = ref({
showSizeChanger: false,
showQuickJumper: true,
pageSize,
current: page,
total,
showTotal: (total) => `${total}`,
onChange: pageChange,
onShowSizeChange: pageSizeChange,
});
function pageChange(p, pz) {
page.value = p;
pageSize.value = pz;
fetch();
}
function pageSizeChange(current, size) {
pageSize.value = size;
fetch();
}
async function handleDelete(id) {
emit('delete', id);
}
</script>

View File

@ -0,0 +1,25 @@
import { ref } from 'vue';
//每行个数
export const grid = ref(12);
// slider属性
export const useSlider = (min = 6, max = 12) => {
// 每行显示个数滑动条
const getMarks = () => {
const l = {};
for (let i = min; i < max + 1; i++) {
l[i] = {
style: {
color: '#fff',
},
label: i,
};
}
return l;
};
return {
min,
max,
marks: getMarks(),
step: 1,
};
};

View File

@ -90,6 +90,9 @@
theme: getDarkMode.value === 'dark' ? 'dark' : 'classic', theme: getDarkMode.value === 'dark' ? 'dark' : 'classic',
lang: unref(getCurrentLang), lang: unref(getCurrentLang),
mode: 'sv', mode: 'sv',
fullscreen: {
index: 520,
},
preview: { preview: {
actions: [], actions: [],
}, },

View File

@ -126,9 +126,6 @@
emit('menuClick', key); emit('menuClick', key);
isClickGo.value = true; isClickGo.value = true;
// const parentPath = await getCurrentParentPath(key);
// menuState.openKeys = [parentPath];
menuState.selectedKeys = [key]; menuState.selectedKeys = [key];
} }

View File

@ -1,13 +1,11 @@
<template> <template>
<MenuItem :key="item.path"> <MenuItem :key="item.path">
<!-- <MenuItem :class="getLevelClass"> -->
<MenuItemContent v-bind="$props" :item="item" /> <MenuItemContent v-bind="$props" :item="item" />
</MenuItem> </MenuItem>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { Menu } from 'ant-design-vue'; import { Menu } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { itemProps } from '../props'; import { itemProps } from '../props';
import MenuItemContent from './MenuItemContent.vue'; import MenuItemContent from './MenuItemContent.vue';
@ -15,20 +13,8 @@
name: 'BasicMenuItem', name: 'BasicMenuItem',
components: { MenuItem: Menu.Item, MenuItemContent }, components: { MenuItem: Menu.Item, MenuItemContent },
props: itemProps, props: itemProps,
setup() // props setup() {
{ return {};
const { prefixCls } = useDesign('basic-menu-item');
// const getLevelClass = computed(() => {
// const { level, theme } = props;
// const levelCls = [`${prefixCls}__level${level}`, theme];
// return levelCls;
// });
return {
prefixCls,
// getLevelClass,
};
}, },
}); });
</script> </script>

View File

@ -19,7 +19,7 @@
width: 520px; width: 520px;
padding-bottom: 0; padding-bottom: 0;
.scrollbar { .ant-modal-body > .scrollbar {
padding: 14px; padding: 14px;
} }

View File

@ -334,6 +334,13 @@
@prefix-cls: ~'@{namespace}-basic-table'; @prefix-cls: ~'@{namespace}-basic-table';
[data-theme='dark'] {
.ant-table-tbody > tr:hover.ant-table-row-selected > td,
.ant-table-tbody > tr.ant-table-row-selected td {
background-color: #262626;
}
}
.@{prefix-cls} { .@{prefix-cls} {
max-width: 100%; max-width: 100%;

View File

@ -1,5 +1,4 @@
import type { App } from 'vue'; import type { App } from 'vue';
// import { Icon } from './Icon';
import { Button } from './Button'; import { Button } from './Button';
import { import {
// Need // Need

View File

@ -45,6 +45,7 @@ export default {
time: 'Relative Time', time: 'Relative Time',
cropperImage: 'Cropper Image', cropperImage: 'Cropper Image',
cardList: 'Card List',
}, },
editor: { editor: {
editor: 'Editor', editor: 'Editor',

View File

@ -44,6 +44,7 @@ export default {
time: '相对时间', time: '相对时间',
cropperImage: '图片裁剪', cropperImage: '图片裁剪',
cardList: '卡片列表',
}, },
editor: { editor: {
editor: '编辑器', editor: '编辑器',

View File

@ -29,14 +29,24 @@ export function createPermissionGuard(router: Router) {
return; return;
} }
const token = userStore.getToken;
// Whitelist can be directly entered // Whitelist can be directly entered
if (whitePathList.includes(to.path as PageEnum)) { if (whitePathList.includes(to.path as PageEnum)) {
if (to.path === LOGIN_PATH && token) {
const isSessionTimeout = userStore.getSessionTimeout;
try {
await userStore.afterLoginAction();
if (!isSessionTimeout) {
next((to.query?.redirect as string) || '/');
return;
}
} catch {}
}
next(); next();
return; return;
} }
const token = userStore.getToken;
// token does not exist // token does not exist
if (!token) { if (!token) {
// You can access without permission. You need to set the routing meta.ignoreAuth to true // You can access without permission. You need to set the routing meta.ignoreAuth to true

View File

@ -534,6 +534,14 @@ const comp: AppRouteModule = {
title: t('routes.demo.comp.loading'), title: t('routes.demo.comp.loading'),
}, },
}, },
{
path: 'cardList',
name: 'CardListDemo',
component: () => import('/@/views/demo/comp/card-list/index.vue'),
meta: {
title: t('routes.demo.comp.cardList'),
},
},
], ],
}; };

View File

@ -14,6 +14,7 @@ import { router } from '/@/router';
import { usePermissionStore } from '/@/store/modules/permission'; import { usePermissionStore } from '/@/store/modules/permission';
import { RouteRecordRaw } from 'vue-router'; import { RouteRecordRaw } from 'vue-router';
import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic'; import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic';
import { isArray } from '/@/utils/is';
interface UserState { interface UserState {
userInfo: Nullable<UserInfo>; userInfo: Nullable<UserInfo>;
@ -117,10 +118,15 @@ export const useUserStore = defineStore({
}, },
async getUserInfoAction(): Promise<UserInfo> { async getUserInfoAction(): Promise<UserInfo> {
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
const { roles } = userInfo; const { roles = [] } = userInfo;
const roleList = roles.map((item) => item.value) as RoleEnum[]; if (isArray(roles)) {
const roleList = roles.map((item) => item.value) as RoleEnum[];
this.setRoleList(roleList);
} else {
userInfo.roles = [];
this.setRoleList([]);
}
this.setUserInfo(userInfo); this.setUserInfo(userInfo);
this.setRoleList(roleList);
return userInfo; return userInfo;
}, },
/** /**

View File

@ -0,0 +1,32 @@
<template>
<PageWrapper title="卡片列表示例" content="基础封装">
<CardList :params="params" :api="demoListApi" @getMethod="getMethod" @delete="handleDel">
<template #header>
<Button type="primary" color="error"> 按钮1 </Button>
<Button type="primary" color="success"> 按钮2 </Button>
</template>
</CardList>
</PageWrapper>
</template>
<script lang="ts" setup>
import { CardList } from '/@/components/CardList';
import { Button } from '/@/components/Button';
import { PageWrapper } from '/@/components/Page';
import { demoListApi } from '/@/api/demo/table';
import { useMessage } from '/@/hooks/web/useMessage';
const { notification } = useMessage();
// api
const params = {};
let reload = () => {};
// fetch;
function getMethod(m: any) {
reload = m;
}
//
function handleDel(id) {
console.log(id);
notification.success({ message: `成功删除${id}` });
reload();
}
</script>

View File

@ -29,7 +29,7 @@
"pm2": "^5.1.1", "pm2": "^5.1.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"ts-node": "^10.2.1", "ts-node": "^10.2.1",
"tsconfig-paths": "^3.10.1", "tsconfig-paths": "^3.11.0",
"tsup": "^4.14.0", "tsup": "^4.14.0",
"typescript": "^4.3.5" "typescript": "^4.3.5"
} }

View File

@ -29,7 +29,7 @@ export default defineConfig({
* Used for animation when the element is displayed * Used for animation when the element is displayed
* @param maxOutput The larger the maxOutput output, the larger the generated css volume * @param maxOutput The larger the maxOutput output, the larger the generated css volume
*/ */
function createEnterPlugin(maxOutput = 10) { function createEnterPlugin(maxOutput = 8) {
const createCss = (index: number, d = 'x') => { const createCss = (index: number, d = 'x') => {
const upd = d.toUpperCase(); const upd = d.toUpperCase();
return { return {