!99 feat: 新增完善 ele 的请求、路由、百度统计、概览、登录、系统管理模块

Merge pull request !99 from puhui999/dev-new
pull/100/MERGE
xingyu 2025-05-12 02:14:15 +00:00 committed by Gitee
commit 9dcd65df9c
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
207 changed files with 19016 additions and 868 deletions

View File

@ -53,8 +53,7 @@
"pinia": "catalog:",
"vue": "catalog:",
"vue-dompurify-html": "catalog:",
"vue-router": "catalog:",
"vxe-table": "catalog:"
"vue-router": "catalog:"
},
"devDependencies": {
"@types/crypto-js": "catalog:"

View File

@ -0,0 +1,79 @@
/* 来自 @vben/plugins/vxe-table style.css */
:root {
--vxe-ui-font-color: hsl(var(--foreground));
--vxe-ui-font-primary-color: hsl(var(--primary));
/* --vxe-ui-font-lighten-color: #babdc0;
--vxe-ui-font-darken-color: #86898e; */
--vxe-ui-font-disabled-color: hsl(var(--foreground) / 50%);
/* base */
--vxe-ui-base-popup-border-color: hsl(var(--border));
--vxe-ui-input-disabled-color: hsl(var(--border) / 60%);
/* --vxe-ui-base-popup-box-shadow: 0px 12px 30px 8px rgb(0 0 0 / 50%); */
/* layout */
--vxe-ui-layout-background-color: hsl(var(--background));
--vxe-ui-table-resizable-line-color: hsl(var(--heavy));
/* --vxe-ui-table-fixed-left-scrolling-box-shadow: 8px 0px 10px -5px hsl(var(--accent));
--vxe-ui-table-fixed-right-scrolling-box-shadow: -8px 0px 10px -5px hsl(var(--accent)); */
/* input */
--vxe-ui-input-border-color: hsl(var(--border));
/* --vxe-ui-input-placeholder-color: #8d9095; */
/* --vxe-ui-input-disabled-background-color: #262727; */
/* loading */
--vxe-ui-loading-background-color: hsl(var(--overlay-content));
/* table */
--vxe-ui-table-header-background-color: hsl(var(--accent));
--vxe-ui-table-border-color: hsl(var(--border));
--vxe-ui-table-row-hover-background-color: hsl(var(--accent-hover));
--vxe-ui-table-row-striped-background-color: hsl(var(--accent) / 60%);
--vxe-ui-table-row-hover-striped-background-color: hsl(var(--accent));
--vxe-ui-table-row-radio-checked-background-color: hsl(var(--accent));
--vxe-ui-table-row-hover-radio-checked-background-color: hsl(
var(--accent-hover)
);
--vxe-ui-table-row-checkbox-checked-background-color: hsl(var(--accent));
--vxe-ui-table-row-hover-checkbox-checked-background-color: hsl(
var(--accent-hover)
);
--vxe-ui-table-row-current-background-color: hsl(var(--accent));
--vxe-ui-table-row-hover-current-background-color: hsl(var(--accent-hover));
--vxe-ui-font-primary-tinge-color: hsl(var(--primary));
--vxe-ui-font-primary-lighten-color: hsl(var(--primary) / 60%);
--vxe-ui-font-primary-darken-color: hsl(var(--primary));
/* height: auto !important; */
/* --vxe-ui-table-fixed-scrolling-box-shadow-color: rgb(0 0 0 / 80%); */
}
.vxe-tools--operate {
margin-right: 0.25rem;
margin-left: 0.75rem;
}
.vxe-table-custom--checkbox-option:hover {
background: none !important;
}
.vxe-toolbar {
padding: 0;
}
.vxe-buttons--wrapper:not(:empty),
.vxe-tools--operate:not(:empty),
.vxe-tools--wrapper:not(:empty) {
padding: 0.6em 0;
}
.vxe-tools--operate:not(:has(button)) {
margin-left: 0;
}

View File

@ -4,7 +4,11 @@ import { h } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { $te } from '@vben/locales';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import {
AsyncComponents,
setupVbenVxeTable,
useVbenVxeGrid,
} from '@vben/plugins/vxe-table';
import { isFunction, isString } from '@vben/utils';
import { Button, Image, Popconfirm, Switch } from 'ant-design-vue';
@ -14,6 +18,8 @@ import { $t } from '#/locales';
import { useVbenForm } from './form';
import '#/adapter/style.css';
setupVbenVxeTable({
configVxeTable: (vxeUI) => {
vxeUI.setConfig({
@ -111,6 +117,7 @@ setupVbenVxeTable({
loading: row[loadingKey] ?? false,
'onUpdate:checked': onChange,
};
async function onChange(newVal: any) {
row[loadingKey] = true;
try {
@ -122,6 +129,7 @@ setupVbenVxeTable({
row[loadingKey] = false;
}
}
return h(Switch, finallyProps);
},
});
@ -280,6 +288,10 @@ setupVbenVxeTable({
});
export { useVbenVxeGrid };
const [VxeTable, VxeColumn, VxeToolbar] = AsyncComponents;
export { VxeColumn, VxeTable, VxeToolbar };
// add by 芋艿from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/adapter/vxe-table.ts#L264-L270
export type OnActionClickParams<T = Recordable<any>> = {
code: string;

View File

@ -17,8 +17,6 @@ import { initComponentAdapter } from './adapter/component';
import App from './app.vue';
import { router } from './router';
import 'vxe-table/styles/cssvar.scss'; // TODO @puhui999这个必须导入哇我看 use-vxe-grid.vue 已经导入了
async function bootstrap(namespace: string) {
// 初始化组件适配器
await initComponentAdapter();

View File

@ -1,14 +1,15 @@
<!-- add by puhui999vxe table 工具栏二次封装提供给 vxe 原生列表使用 -->
<script setup lang="ts">
import type { VxeToolbarInstance } from 'vxe-table';
import type { VxeToolbarInstance } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { useContentMaximize, useRefresh } from '@vben/hooks';
import { Fullscreen, RefreshCw, Search } from '@vben/icons';
import { Expand, MsRefresh, Search, TMinimize } from '@vben/icons';
import { Button } from 'ant-design-vue';
import { VxeToolbar } from 'vxe-table';
import { Button, Tooltip } from 'ant-design-vue';
import { VxeToolbar } from '#/adapter/vxe-table';
/** 列表工具栏封装 */
defineOptions({ name: 'TableToolbar' });
@ -20,7 +21,8 @@ const props = defineProps<{
const emits = defineEmits(['update:hiddenSearch']);
const toolbarRef = ref<VxeToolbarInstance>();
const { toggleMaximizeAndTabbarHidden } = useContentMaximize();
const { toggleMaximizeAndTabbarHidden, contentIsMaximize } =
useContentMaximize();
const { refresh } = useRefresh();
/** 隐藏搜索栏 */
@ -37,20 +39,41 @@ defineExpose({
<VxeToolbar ref="toolbarRef" custom>
<template #toolPrefix>
<slot></slot>
<!-- TODO @puhui999貌似 icon 没和 vxe 对上可以考虑用 /Users/yunai/Java/yudao-ui-admin-vben-v5/packages/icons/src/iconify -->
<Button class="ml-2 font-[8px]" shape="circle" @click="onHiddenSearchBar">
<Search :size="15" />
</Button>
<Button class="ml-2 font-[8px]" shape="circle" @click="refresh">
<RefreshCw :size="15" />
</Button>
<Button
class="ml-2 font-[8px]"
shape="circle"
@click="toggleMaximizeAndTabbarHidden"
>
<Fullscreen :size="15" />
</Button>
<Tooltip placement="bottom">
<template #title>
<div class="max-w-[200px]">搜索</div>
</template>
<Button
class="ml-2 font-[8px]"
shape="circle"
@click="onHiddenSearchBar"
>
<Search :size="15" />
</Button>
</Tooltip>
<Tooltip placement="bottom">
<template #title>
<div class="max-w-[200px]">刷新</div>
</template>
<Button class="ml-2 font-[8px]" shape="circle" @click="refresh">
<MsRefresh :size="15" />
</Button>
</Tooltip>
<Tooltip placement="bottom">
<template #title>
<div class="max-w-[200px]">
{{ contentIsMaximize ? '还原' : '全屏' }}
</div>
</template>
<Button
class="ml-2 font-[8px]"
shape="circle"
@click="toggleMaximizeAndTabbarHidden"
>
<Expand v-if="!contentIsMaximize" :size="15" />
<TMinimize v-else :size="15" />
</Button>
</Tooltip>
</template>
</VxeToolbar>
</template>

View File

@ -0,0 +1 @@
export * from './use-table-toolbar';

View File

@ -0,0 +1,42 @@
import type { VxeTableInstance, VxeToolbarInstance } from '#/adapter/vxe-table';
import type { TableToolbar } from '#/components/table-toolbar';
import { ref, watch } from 'vue';
export function useTableToolbar() {
const hiddenSearchBar = ref(false); // 隐藏搜索栏
const tableToolbarRef = ref<InstanceType<typeof TableToolbar>>();
const tableRef = ref<VxeTableInstance>();
const isBound = ref<boolean>(false);
/** 挂载 toolbar 工具栏 */
async function bindTableToolbar() {
const table = tableRef.value;
const tableToolbar = tableToolbarRef.value;
if (table && tableToolbar) {
setTimeout(async () => {
const toolbar = tableToolbar.getToolbarRef();
if (!toolbar) {
console.error('[toolbar 挂载失败] Table toolbar not found');
}
await table.connect(toolbar as VxeToolbarInstance);
isBound.value = true;
}, 1000); // 延迟挂载确保 toolbar 正确挂载
}
}
watch(
() => tableRef.value,
(val) => {
if (!val || isBound.value) return;
bindTableToolbar();
},
{ immediate: true },
);
return {
hiddenSearchBar,
tableToolbarRef,
tableRef,
};
}

View File

@ -1,9 +1,7 @@
<script lang="ts" setup>
import type { VxeTableInstance } from 'vxe-table';
import type { Demo01ContactApi } from '#/api/infra/demo/demo01';
import { h, nextTick, onMounted, reactive, ref } from 'vue';
import { h, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
@ -22,8 +20,8 @@ import {
RangePicker,
Select,
} from 'ant-design-vue';
import { VxeColumn, VxeTable } from 'vxe-table';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import {
deleteDemo01Contact,
exportDemo01Contact,
@ -32,6 +30,7 @@ import {
import { ContentWrap } from '#/components/content-wrap';
import { DictTag } from '#/components/dict-tag';
import { TableToolbar } from '#/components/table-toolbar';
import { useTableToolbar } from '#/hooks';
import { $t } from '#/locales';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
@ -120,20 +119,10 @@ async function onExport() {
}
}
/** 隐藏搜索栏 */
const hiddenSearchBar = ref(false);
const tableToolbarRef = ref<InstanceType<typeof TableToolbar>>();
const tableRef = ref<VxeTableInstance>();
/** 初始化 */
onMounted(async () => {
await getList();
await nextTick();
// toolbar
const table = tableRef.value;
const tableToolbar = tableToolbarRef.value;
if (table && tableToolbar) {
await table.connect(tableToolbar.getToolbarRef()!);
}
const { hiddenSearchBar, tableToolbarRef, tableRef } = useTableToolbar();
onMounted(() => {
getList();
});
</script>
@ -192,7 +181,6 @@ onMounted(async () => {
<!-- 列表 -->
<ContentWrap title="示例联系人">
<!-- TODO @puhui999暗黑模式下会有个黑底 -->
<template #extra>
<TableToolbar
ref="tableToolbarRef"

View File

@ -1,9 +1,7 @@
<script lang="ts" setup>
import type { VxeTableInstance } from 'vxe-table';
import type { Demo02CategoryApi } from '#/api/infra/demo/demo02';
import { h, nextTick, onMounted, reactive, ref } from 'vue';
import { h, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
@ -15,8 +13,8 @@ import {
} from '@vben/utils';
import { Button, Form, Input, message, RangePicker } from 'ant-design-vue';
import { VxeColumn, VxeTable } from 'vxe-table';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import {
deleteDemo02Category,
exportDemo02Category,
@ -24,6 +22,7 @@ import {
} from '#/api/infra/demo/demo02';
import { ContentWrap } from '#/components/content-wrap';
import { TableToolbar } from '#/components/table-toolbar';
import { useTableToolbar } from '#/hooks';
import { $t } from '#/locales';
import { getRangePickerDefaultProps } from '#/utils';
@ -112,11 +111,6 @@ async function onExport() {
}
}
/** 隐藏搜索栏 */
const hiddenSearchBar = ref(false);
const tableToolbarRef = ref<InstanceType<typeof TableToolbar>>();
const tableRef = ref<VxeTableInstance>();
/** 切换树形展开/收缩状态 */
const isExpanded = ref(true);
function toggleExpand() {
@ -125,15 +119,9 @@ function toggleExpand() {
}
/** 初始化 */
onMounted(async () => {
await getList();
await nextTick();
// toolbar
const table = tableRef.value;
const tableToolbar = tableToolbarRef.value;
if (table && tableToolbar) {
await table.connect(tableToolbar.getToolbarRef()!);
}
const { hiddenSearchBar, tableToolbarRef, tableRef } = useTableToolbar();
onMounted(() => {
getList();
});
</script>

View File

@ -1,9 +1,7 @@
<script lang="ts" setup>
import type { VxeTableInstance } from 'vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, nextTick, onMounted, reactive, ref } from 'vue';
import { h, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
@ -24,8 +22,8 @@ import {
Select,
Tabs,
} from 'ant-design-vue';
import { VxeColumn, VxeTable } from 'vxe-table';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import {
deleteDemo03Student,
exportDemo03Student,
@ -34,6 +32,7 @@ import {
import { ContentWrap } from '#/components/content-wrap';
import { DictTag } from '#/components/dict-tag';
import { TableToolbar } from '#/components/table-toolbar';
import { useTableToolbar } from '#/hooks';
import { $t } from '#/locales';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
@ -137,21 +136,10 @@ async function onExport() {
}
}
/** 隐藏搜索栏 */
const hiddenSearchBar = ref(false);
const tableToolbarRef = ref<InstanceType<typeof TableToolbar>>();
const tableRef = ref<VxeTableInstance>();
/** 初始化 */
onMounted(async () => {
await getList();
await nextTick();
// toolbar
const table = tableRef.value;
const tableToolbar = tableToolbarRef.value;
if (table && tableToolbar) {
await table.connect(tableToolbar.getToolbarRef()!);
}
const { hiddenSearchBar, tableToolbarRef, tableRef } = useTableToolbar();
onMounted(() => {
getList();
});
</script>

View File

@ -1,6 +1,4 @@
<script lang="ts" setup>
import type { VxeTableInstance } from 'vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, nextTick, onMounted, reactive, ref, watch } from 'vue';
@ -17,14 +15,15 @@ import {
Pagination,
RangePicker,
} from 'ant-design-vue';
import { VxeColumn, VxeTable } from 'vxe-table';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import {
deleteDemo03Course,
getDemo03CoursePage,
} from '#/api/infra/demo/demo03/erp';
import { ContentWrap } from '#/components/content-wrap';
import { TableToolbar } from '#/components/table-toolbar';
import { useTableToolbar } from '#/hooks';
import { $t } from '#/locales';
import { getRangePickerDefaultProps } from '#/utils';
@ -129,21 +128,10 @@ watch(
{ immediate: true },
);
/** 隐藏搜索栏 */
const hiddenSearchBar = ref(false);
const tableToolbarRef = ref<InstanceType<typeof TableToolbar>>();
const tableRef = ref<VxeTableInstance>();
/** 初始化 */
onMounted(async () => {
await getList();
await nextTick();
// toolbar
const table = tableRef.value;
const tableToolbar = tableToolbarRef.value;
if (table && tableToolbar) {
await table.connect(tableToolbar.getToolbarRef()!);
}
const { hiddenSearchBar, tableToolbarRef, tableRef } = useTableToolbar();
onMounted(() => {
getList();
});
</script>

View File

@ -1,6 +1,4 @@
<script lang="ts" setup>
import type { VxeTableInstance } from 'vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, nextTick, onMounted, reactive, ref, watch } from 'vue';
@ -17,14 +15,15 @@ import {
Pagination,
RangePicker,
} from 'ant-design-vue';
import { VxeColumn, VxeTable } from 'vxe-table';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import {
deleteDemo03Grade,
getDemo03GradePage,
} from '#/api/infra/demo/demo03/erp';
import { ContentWrap } from '#/components/content-wrap';
import { TableToolbar } from '#/components/table-toolbar';
import { useTableToolbar } from '#/hooks';
import { $t } from '#/locales';
import { getRangePickerDefaultProps } from '#/utils';
@ -129,21 +128,10 @@ watch(
{ immediate: true },
);
/** 隐藏搜索栏 */
const hiddenSearchBar = ref(false);
const tableToolbarRef = ref<InstanceType<typeof TableToolbar>>();
const tableRef = ref<VxeTableInstance>();
/** 初始化 */
onMounted(async () => {
await getList();
await nextTick();
// toolbar
const table = tableRef.value;
const tableToolbar = tableToolbarRef.value;
if (table && tableToolbar) {
await table.connect(tableToolbar.getToolbarRef()!);
}
const { hiddenSearchBar, tableToolbarRef, tableRef } = useTableToolbar();
onMounted(() => {
getList();
});
</script>

View File

@ -1,9 +1,7 @@
<script lang="ts" setup>
import type { VxeTableInstance } from 'vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { h, nextTick, onMounted, reactive, ref } from 'vue';
import { h, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
@ -24,8 +22,8 @@ import {
Select,
Tabs,
} from 'ant-design-vue';
import { VxeColumn, VxeTable } from 'vxe-table';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import {
deleteDemo03Student,
exportDemo03Student,
@ -34,6 +32,7 @@ import {
import { ContentWrap } from '#/components/content-wrap';
import { DictTag } from '#/components/dict-tag';
import { TableToolbar } from '#/components/table-toolbar';
import { useTableToolbar } from '#/hooks';
import { $t } from '#/locales';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
@ -133,21 +132,10 @@ async function onExport() {
}
}
/** 隐藏搜索栏 */
const hiddenSearchBar = ref(false);
const tableToolbarRef = ref<InstanceType<typeof TableToolbar>>();
const tableRef = ref<VxeTableInstance>();
/** 初始化 */
onMounted(async () => {
await getList();
await nextTick();
// toolbar
const table = tableRef.value;
const tableToolbar = tableToolbarRef.value;
if (table && tableToolbar) {
await table.connect(tableToolbar.getToolbarRef()!);
}
const { hiddenSearchBar, tableToolbarRef, tableRef } = useTableToolbar();
onMounted(() => {
getList();
});
</script>

View File

@ -1,6 +1,5 @@
<script lang="ts" setup>
import type { VxeTableInstance } from 'vxe-table';
import type { VxeTableInstance } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { h, ref, watch } from 'vue';
@ -8,8 +7,8 @@ import { h, ref, watch } from 'vue';
import { Plus } from '@vben/icons';
import { Button, Input } from 'ant-design-vue';
import { VxeColumn, VxeTable } from 'vxe-table';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/normal';
import { $t } from '#/locales';

View File

@ -5,9 +5,9 @@ import { nextTick, ref, watch } from 'vue';
import { formatDateTime } from '@vben/utils';
import { VxeColumn, VxeTable } from 'vxe-table';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/normal';
import { ContentWrap } from '#/components/content-wrap';
const props = defineProps<{
studentId?: number; //

View File

@ -5,8 +5,7 @@ import { nextTick, ref, watch } from 'vue';
import { formatDateTime } from '@vben/utils';
import { VxeColumn, VxeTable } from 'vxe-table';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/normal';
const props = defineProps<{

View File

@ -1,9 +1,7 @@
<script lang="ts" setup>
import type { VxeTableInstance } from 'vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { h, nextTick, onMounted, reactive, ref } from 'vue';
import { h, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus } from '@vben/icons';
@ -23,8 +21,8 @@ import {
RangePicker,
Select,
} from 'ant-design-vue';
import { VxeColumn, VxeTable } from 'vxe-table';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import {
deleteDemo03Student,
exportDemo03Student,
@ -33,6 +31,7 @@ import {
import { ContentWrap } from '#/components/content-wrap';
import { DictTag } from '#/components/dict-tag';
import { TableToolbar } from '#/components/table-toolbar';
import { useTableToolbar } from '#/hooks';
import { $t } from '#/locales';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
@ -127,21 +126,10 @@ async function onExport() {
}
}
/** 隐藏搜索栏 */
const hiddenSearchBar = ref(false);
const tableToolbarRef = ref<InstanceType<typeof TableToolbar>>();
const tableRef = ref<VxeTableInstance>();
/** 初始化 */
onMounted(async () => {
await getList();
await nextTick();
// toolbar
const table = tableRef.value;
const tableToolbar = tableToolbarRef.value;
if (table && tableToolbar) {
await table.connect(tableToolbar.getToolbarRef()!);
}
const { hiddenSearchBar, tableToolbarRef, tableRef } = useTableToolbar();
onMounted(() => {
getList();
});
</script>

View File

@ -1,6 +1,5 @@
<script lang="ts" setup>
import type { VxeTableInstance } from 'vxe-table';
import type { VxeTableInstance } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { h, ref, watch } from 'vue';
@ -8,8 +7,8 @@ import { h, ref, watch } from 'vue';
import { Plus } from '@vben/icons';
import { Button, Input } from 'ant-design-vue';
import { VxeColumn, VxeTable } from 'vxe-table';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/normal';
import { $t } from '#/locales';

View File

@ -1,8 +1,26 @@
# 应用标题
VITE_APP_TITLE=Vben Admin Ele
VITE_APP_TITLE=芋道管理系统
# 应用命名空间用于缓存、store等功能的前缀确保隔离
VITE_APP_NAMESPACE=vben-web-ele
VITE_APP_NAMESPACE=yudao-vben-ele
# 对store进行加密的密钥在将store持久化到localStorage时会使用该密钥进行加密
VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
# 是否开启模拟数据
VITE_NITRO_MOCK=false
# 租户开关
VITE_APP_TENANT_ENABLE=true
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=false
# 文档地址的开关
VITE_APP_DOCALERT_ENABLE=true
# 百度统计
VITE_APP_BAIDU_CODE = e98f2eab6ceb8688bc6d8fc5332ff093
# GoView域名
VITE_GOVIEW_URL='http://127.0.0.1:3000'

View File

@ -3,14 +3,19 @@ VITE_PORT=5777
VITE_BASE=/
# 请求路径
VITE_BASE_URL=http://127.0.0.1:48080
# 接口地址
VITE_GLOB_API_URL=/api
# 是否开启 Nitro Mock服务true 为开启false 为关闭
VITE_NITRO_MOCK=true
VITE_GLOB_API_URL=/admin-api
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=server
# 是否打开 devtoolstrue 为打开false 为关闭
VITE_DEVTOOLS=false
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true
# 默认登录用户名
VITE_APP_DEFAULT_USERNAME=admin
# 默认登录密码
VITE_APP_DEFAULT_PASSWORD=admin123

View File

@ -1,7 +1,11 @@
VITE_BASE=/
# 请求路径
VITE_BASE_URL=http://127.0.0.1:48080
# 接口地址
VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
VITE_GLOB_API_URL=http://127.0.0.1:48080/admin-api
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=server
# 是否开启压缩,可以设置为 none, brotli, gzip
VITE_COMPRESS=none

View File

@ -15,13 +15,12 @@
<title><%= VITE_APP_TITLE %></title>
<link rel="icon" href="/favicon.ico" />
<script>
// 生产环境下注入百度统计
if (window._VBEN_ADMIN_PRO_APP_CONF_) {
var HM_ID = '<%= VITE_APP_BAIDU_CODE %>';
if (HM_ID) {
var _hmt = _hmt || [];
(function () {
var hm = document.createElement('script');
hm.src =
'https://hm.baidu.com/hm.js?97352b16ed2df8c3860cf5a1a65fb4dd';
hm.src = 'https://hm.baidu.com/hm.js?' + HM_ID;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(hm, s);
})();

View File

@ -21,6 +21,8 @@ import { $t } from '@vben/locales';
import { ElNotification } from 'element-plus';
import { FileUpload, ImageUpload } from '#/components/upload';
const ElButton = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/button/index'),
@ -167,7 +169,9 @@ export type ComponentType =
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'FileUpload'
| 'IconPicker'
| 'ImageUpload'
| 'Input'
| 'InputNumber'
| 'RadioGroup'
@ -315,6 +319,8 @@ async function initComponentAdapter() {
},
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
Upload: ElUpload,
FileUpload,
ImageUpload,
};
// 将组件注册到全局共享状态中

View File

@ -1,8 +1,20 @@
import type { Recordable } from '@vben/types';
import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import { IconifyIcon } from '@vben/icons';
import { $te } from '@vben/locales';
import {
AsyncComponents,
setupVbenVxeTable,
useVbenVxeGrid,
} from '@vben/plugins/vxe-table';
import { isFunction, isString } from '@vben/utils';
import { ElButton, ElImage } from 'element-plus';
import { ElButton, ElImage, ElPopconfirm, ElSwitch } from 'element-plus';
import { DictTag } from '#/components/dict-tag';
import { $t } from '#/locales';
import { useVbenForm } from './form';
@ -20,16 +32,32 @@ setupVbenVxeTable({
// 全局禁用vxe-table的表单配置使用formOptions
enabled: false,
},
toolbarConfig: {
import: false, // 是否导入
export: false, // 是否导出
refresh: true, // 是否刷新
print: false, // 是否打印
zoom: true, // 是否缩放
custom: true, // 是否自定义配置
},
customConfig: {
mode: 'modal',
},
proxyConfig: {
autoLoad: true,
response: {
result: 'items',
result: 'list',
total: 'total',
list: 'items',
},
showActiveMsg: true,
showResponseMsg: false,
},
pagerConfig: {
enabled: true,
},
sortConfig: {
multiple: true,
},
round: true,
showOverflow: true,
size: 'small',
@ -57,12 +85,209 @@ setupVbenVxeTable({
},
});
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
// vxeUI.formats.add
// 表格配置项可以用 cellRender: { name: 'CellDict', props:{dictType: ''} },
vxeUI.renderer.add('CellDict', {
renderTableDefault(renderOpts, params) {
const { props } = renderOpts;
const { column, row } = params;
if (!props) {
return '';
}
// 使用 DictTag 组件替代原来的实现
return h(DictTag, {
type: props.type,
value: row[column.field]?.toString(),
});
},
});
// 表格配置项可以用 cellRender: { name: 'CellSwitch', props: { beforeChange: () => {} } },
vxeUI.renderer.add('CellSwitch', {
renderTableDefault({ attrs, props }, { column, row }) {
const loadingKey = `__loading_${column.field}`;
const finallyProps = {
activeText: $t('common.enabled'),
inactiveText: $t('common.disabled'),
activeValue: 1,
inactiveValue: 0,
...props,
modelValue: row[column.field],
loading: row[loadingKey] ?? false,
'onUpdate:modelValue': onChange,
};
async function onChange(newVal: any) {
row[loadingKey] = true;
try {
const result = await attrs?.beforeChange?.(newVal, row);
if (result !== false) {
row[column.field] = newVal;
}
} finally {
row[loadingKey] = false;
}
}
return h(ElSwitch, finallyProps);
},
});
// 注册表格的操作按钮渲染器 cellRender: { name: 'CellOperation', options: ['edit', 'delete'] }
vxeUI.renderer.add('CellOperation', {
renderTableDefault({ attrs, options, props }, { column, row }) {
const defaultProps = { size: 'small', type: 'primary', ...props };
let align = 'end';
switch (column.align) {
case 'center': {
align = 'center';
break;
}
case 'left': {
align = 'start';
break;
}
default: {
align = 'end';
break;
}
}
const presets: Recordable<Recordable<any>> = {
delete: {
type: 'danger',
text: $t('common.delete'),
},
edit: {
text: $t('common.edit'),
},
};
const operations: Array<Recordable<any>> = (
options || ['edit', 'delete']
)
.map((opt) => {
if (isString(opt)) {
return presets[opt]
? { code: opt, ...presets[opt], ...defaultProps }
: {
code: opt,
text: $te(`common.${opt}`) ? $t(`common.${opt}`) : opt,
...defaultProps,
};
} else {
return { ...defaultProps, ...presets[opt.code], ...opt };
}
})
.map((opt) => {
const optBtn: Recordable<any> = {};
Object.keys(opt).forEach((key) => {
optBtn[key] = isFunction(opt[key]) ? opt[key](row) : opt[key];
});
return optBtn;
})
.filter((opt) => opt.show !== false);
function renderBtn(opt: Recordable<any>, listen = true) {
return h(
ElButton,
{
...props,
...opt,
icon: undefined,
onClick: listen
? () =>
attrs?.onClick?.({
code: opt.code,
row,
})
: undefined,
},
{
default: () => {
const content = [];
if (opt.icon) {
content.push(
h(IconifyIcon, { class: 'size-5', icon: opt.icon }),
);
}
content.push(opt.text);
return content;
},
},
);
}
function renderConfirm(opt: Recordable<any>) {
return h(
ElPopconfirm,
{
title: $t('ui.actionTitle.delete', [attrs?.nameTitle || '']),
width: 'auto',
'popper-class': 'popper-top-left',
onConfirm: () => {
attrs?.onClick?.({
code: opt.code,
row,
});
},
},
{
reference: () => renderBtn({ ...opt }, false),
default: () =>
h(
'div',
{ class: 'truncate' },
$t('ui.actionMessage.deleteConfirm', [
row[attrs?.nameField || 'name'],
]),
),
},
);
}
const btns = operations.map((opt) =>
opt.code === 'delete' ? renderConfirm(opt) : renderBtn(opt),
);
return h(
'div',
{
class: 'flex table-operations',
style: { justifyContent: align },
},
btns,
);
},
});
// 添加数量格式化,例如金额
vxeUI.formats.add('formatAmount', {
cellFormatMethod({ cellValue }, digits = 2) {
if (cellValue === null || cellValue === undefined) {
return '';
}
if (isString(cellValue)) {
cellValue = Number.parseFloat(cellValue);
}
// 如果非 number则直接返回空串
if (Number.isNaN(cellValue)) {
return '';
}
return cellValue.toFixed(digits);
},
});
},
useVbenForm,
});
export { useVbenVxeGrid };
const [VxeTable, VxeColumn, VxeToolbar] = AsyncComponents;
export { VxeColumn, VxeTable, VxeToolbar };
// 导出操作按钮的回调函数类型
export type OnActionClickParams<T = Recordable<any>> = {
code: string;
row: T;
};
export type OnActionClickFn<T = Recordable<any>> = (
params: OnActionClickParams<T>,
) => void;
export type * from '@vben/plugins/vxe-table';

View File

@ -1,3 +1,5 @@
import type { AuthPermissionInfo } from '@vben/types';
import { baseRequestClient, requestClient } from '#/api/request';
export namespace AuthApi {
@ -5,47 +7,151 @@ export namespace AuthApi {
export interface LoginParams {
password?: string;
username?: string;
captchaVerification?: string;
// 绑定社交登录时,需要传递如下参数
socialType?: number;
socialCode?: string;
socialState?: string;
}
/** 登录接口返回值 */
export interface LoginResult {
accessToken: string;
refreshToken: string;
userId: number;
expiresTime: number;
}
export interface RefreshTokenResult {
data: string;
status: number;
/** 租户信息返回值 */
export interface TenantResult {
id: number;
name: string;
}
/** 手机验证码获取接口参数 */
export interface SmsCodeParams {
mobile: string;
scene: number;
}
/** 手机验证码登录接口参数 */
export interface SmsLoginParams {
mobile: string;
code: string;
}
/** 注册接口参数 */
export interface RegisterParams {
username: string;
password: string;
captchaVerification: string;
}
/** 重置密码接口参数 */
export interface ResetPasswordParams {
password: string;
mobile: string;
code: string;
}
/** 社交快捷登录接口参数 */
export interface SocialLoginParams {
type: number;
code: string;
state: string;
}
}
/**
*
*/
/** 登录 */
export async function loginApi(data: AuthApi.LoginParams) {
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
return requestClient.post<AuthApi.LoginResult>('/system/auth/login', data);
}
/**
* accessToken
*/
export async function refreshTokenApi() {
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
withCredentials: true,
/** 刷新 accessToken */
export async function refreshTokenApi(refreshToken: string) {
return baseRequestClient.post(
`/system/auth/refresh-token?refreshToken=${refreshToken}`,
);
}
/** 退出登录 */
export async function logoutApi(accessToken: string) {
return baseRequestClient.post(
'/system/auth/logout',
{},
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
);
}
/** 获取权限信息 */
export async function getAuthPermissionInfoApi() {
return requestClient.get<AuthPermissionInfo>(
'/system/auth/get-permission-info',
);
}
/** 获取租户列表 */
export async function getTenantSimpleList() {
return requestClient.get<AuthApi.TenantResult[]>(
`/system/tenant/simple-list`,
);
}
/** 使用租户域名,获得租户信息 */
export async function getTenantByWebsite(website: string) {
return requestClient.get<AuthApi.TenantResult>(
`/system/tenant/get-by-website?website=${website}`,
);
}
/** 获取验证码 */
export async function getCaptcha(data: any) {
return baseRequestClient.post('/system/captcha/get', data);
}
/** 校验验证码 */
export async function checkCaptcha(data: any) {
return baseRequestClient.post('/system/captcha/check', data);
}
/** 获取登录验证码 */
export async function sendSmsCode(data: AuthApi.SmsCodeParams) {
return requestClient.post('/system/auth/send-sms-code', data);
}
/** 短信验证码登录 */
export async function smsLogin(data: AuthApi.SmsLoginParams) {
return requestClient.post('/system/auth/sms-login', data);
}
/** 注册 */
export async function register(data: AuthApi.RegisterParams) {
return requestClient.post('/system/auth/register', data);
}
/** 通过短信重置密码 */
export async function smsResetPassword(data: AuthApi.ResetPasswordParams) {
return requestClient.post('/system/auth/reset-password', data);
}
/** 社交授权的跳转 */
export async function socialAuthRedirect(type: number, redirectUri: string) {
return requestClient.get('/system/auth/social-auth-redirect', {
params: {
type,
redirectUri,
},
});
}
/**
* 退
*/
export async function logoutApi() {
return baseRequestClient.post('/auth/logout', {
withCredentials: true,
});
}
/**
*
*/
export async function getAccessCodesApi() {
return requestClient.get<string[]>('/auth/codes');
/** 社交快捷登录 */
export async function socialLogin(data: AuthApi.SocialLoginParams) {
return requestClient.post<AuthApi.LoginResult>(
'/system/auth/social-login',
data,
);
}

View File

@ -1,3 +1 @@
export * from './auth';
export * from './menu';
export * from './user';

View File

@ -1,10 +0,0 @@
import type { RouteRecordStringComponent } from '@vben/types';
import { requestClient } from '#/api/request';
/**
*
*/
export async function getAllMenusApi() {
return requestClient.get<RouteRecordStringComponent[]>('/menu/all');
}

View File

@ -1,10 +0,0 @@
import type { UserInfo } from '@vben/types';
import { requestClient } from '#/api/request';
/**
*
*/
export async function getUserInfoApi() {
return requestClient.get<UserInfo>('/user/info');
}

View File

@ -0,0 +1,44 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace InfraApiAccessLogApi {
/** API 访问日志信息 */
export interface ApiAccessLog {
id: number;
traceId: string;
userId: number;
userType: number;
applicationName: string;
requestMethod: string;
requestParams: string;
responseBody: string;
requestUrl: string;
userIp: string;
userAgent: string;
operateModule: string;
operateName: string;
operateType: number;
beginTime: string;
endTime: string;
duration: number;
resultCode: number;
resultMsg: string;
createTime: string;
}
}
/** 查询 API 访问日志列表 */
export function getApiAccessLogPage(params: PageParam) {
return requestClient.get<PageResult<InfraApiAccessLogApi.ApiAccessLog>>(
'/infra/api-access-log/page',
{ params },
);
}
/** 导出 API 访问日志 */
export function exportApiAccessLog(params: any) {
return requestClient.download('/infra/api-access-log/export-excel', {
params,
});
}

View File

@ -0,0 +1,55 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace InfraApiErrorLogApi {
/** API 错误日志信息 */
export interface ApiErrorLog {
id: number;
traceId: string;
userId: number;
userType: number;
applicationName: string;
requestMethod: string;
requestParams: string;
requestUrl: string;
userIp: string;
userAgent: string;
exceptionTime: string;
exceptionName: string;
exceptionMessage: string;
exceptionRootCauseMessage: string;
exceptionStackTrace: string;
exceptionClassName: string;
exceptionFileName: string;
exceptionMethodName: string;
exceptionLineNumber: number;
processUserId: number;
processStatus: number;
processTime: string;
resultCode: number;
createTime: string;
}
}
/** 查询 API 错误日志列表 */
export function getApiErrorLogPage(params: PageParam) {
return requestClient.get<PageResult<InfraApiErrorLogApi.ApiErrorLog>>(
'/infra/api-error-log/page',
{ params },
);
}
/** 更新 API 错误日志的处理状态 */
export function updateApiErrorLogStatus(id: number, processStatus: number) {
return requestClient.put(
`/infra/api-error-log/update-status?id=${id}&processStatus=${processStatus}`,
);
}
/** 导出 API 错误日志 */
export function exportApiErrorLog(params: any) {
return requestClient.download('/infra/api-error-log/export-excel', {
params,
});
}

View File

@ -0,0 +1,157 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace InfraCodegenApi {
/** 代码生成表定义 */
export interface CodegenTable {
id: number;
tableId: number;
isParentMenuIdValid: boolean;
dataSourceConfigId: number;
scene: number;
tableName: string;
tableComment: string;
remark: string;
moduleName: string;
businessName: string;
className: string;
classComment: string;
author: string;
createTime: Date;
updateTime: Date;
templateType: number;
parentMenuId: number;
}
/** 代码生成字段定义 */
export interface CodegenColumn {
id: number;
tableId: number;
columnName: string;
dataType: string;
columnComment: string;
nullable: number;
primaryKey: number;
ordinalPosition: number;
javaType: string;
javaField: string;
dictType: string;
example: string;
createOperation: number;
updateOperation: number;
listOperation: number;
listOperationCondition: string;
listOperationResult: number;
htmlType: string;
}
/** 数据库表定义 */
export interface DatabaseTable {
name: string;
comment: string;
}
/** 代码生成详情 */
export interface CodegenDetail {
table: CodegenTable;
columns: CodegenColumn[];
}
/** 代码预览 */
export interface CodegenPreview {
filePath: string;
code: string;
}
/** 更新代码生成请求 */
export interface CodegenUpdateReqVO {
table: any | CodegenTable;
columns: CodegenColumn[];
}
/** 创建代码生成请求 */
export interface CodegenCreateListReqVO {
dataSourceConfigId?: number;
tableNames: string[];
}
}
/** 查询列表代码生成表定义 */
export function getCodegenTableList(dataSourceConfigId: number) {
return requestClient.get<InfraCodegenApi.CodegenTable[]>(
'/infra/codegen/table/list?',
{
params: { dataSourceConfigId },
},
);
}
/** 查询列表代码生成表定义 */
export function getCodegenTablePage(params: PageParam) {
return requestClient.get<PageResult<InfraCodegenApi.CodegenTable>>(
'/infra/codegen/table/page',
{ params },
);
}
/** 查询详情代码生成表定义 */
export function getCodegenTable(tableId: number) {
return requestClient.get<InfraCodegenApi.CodegenDetail>(
'/infra/codegen/detail',
{
params: { tableId },
},
);
}
/** 修改代码生成表定义 */
export function updateCodegenTable(data: InfraCodegenApi.CodegenUpdateReqVO) {
return requestClient.put('/infra/codegen/update', data);
}
/** 基于数据库的表结构,同步数据库的表和字段定义 */
export function syncCodegenFromDB(tableId: number) {
return requestClient.put('/infra/codegen/sync-from-db', {
params: { tableId },
});
}
/** 预览生成代码 */
export function previewCodegen(tableId: number) {
return requestClient.get<InfraCodegenApi.CodegenPreview[]>(
'/infra/codegen/preview',
{
params: { tableId },
},
);
}
/** 下载生成代码 */
export function downloadCodegen(tableId: number) {
return requestClient.download('/infra/codegen/download', {
params: { tableId },
});
}
/** 获得表定义 */
export function getSchemaTableList(params: any) {
return requestClient.get<InfraCodegenApi.DatabaseTable[]>(
'/infra/codegen/db/table/list',
{ params },
);
}
/** 基于数据库的表结构,创建代码生成器的表定义 */
export function createCodegenList(
data: InfraCodegenApi.CodegenCreateListReqVO,
) {
return requestClient.post('/infra/codegen/create-list', data);
}
/** 删除代码生成表定义 */
export function deleteCodegenTable(tableId: number) {
return requestClient.delete('/infra/codegen/delete', {
params: { tableId },
});
}

View File

@ -0,0 +1,62 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace InfraConfigApi {
/** 参数配置信息 */
export interface Config {
id?: number;
category: string;
name: string;
key: string;
value: string;
type: number;
visible: boolean;
remark: string;
createTime?: Date;
}
}
/** 查询参数列表 */
export function getConfigPage(params: PageParam) {
return requestClient.get<PageResult<InfraConfigApi.Config>>(
'/infra/config/page',
{
params,
},
);
}
/** 查询参数详情 */
export function getConfig(id: number) {
return requestClient.get<InfraConfigApi.Config>(`/infra/config/get?id=${id}`);
}
/** 根据参数键名查询参数值 */
export function getConfigKey(configKey: string) {
return requestClient.get<string>(
`/infra/config/get-value-by-key?key=${configKey}`,
);
}
/** 新增参数 */
export function createConfig(data: InfraConfigApi.Config) {
return requestClient.post('/infra/config/create', data);
}
/** 修改参数 */
export function updateConfig(data: InfraConfigApi.Config) {
return requestClient.put('/infra/config/update', data);
}
/** 删除参数 */
export function deleteConfig(id: number) {
return requestClient.delete(`/infra/config/delete?id=${id}`);
}
/** 导出参数 */
export function exportConfig(params: any) {
return requestClient.download('/infra/config/export', {
params,
});
}

View File

@ -0,0 +1,46 @@
import { requestClient } from '#/api/request';
export namespace InfraDataSourceConfigApi {
/** 数据源配置信息 */
export interface DataSourceConfig {
id?: number;
name: string;
url: string;
username: string;
password: string;
createTime?: Date;
}
}
/** 查询数据源配置列表 */
export function getDataSourceConfigList() {
return requestClient.get<InfraDataSourceConfigApi.DataSourceConfig[]>(
'/infra/data-source-config/list',
);
}
/** 查询数据源配置详情 */
export function getDataSourceConfig(id: number) {
return requestClient.get<InfraDataSourceConfigApi.DataSourceConfig>(
`/infra/data-source-config/get?id=${id}`,
);
}
/** 新增数据源配置 */
export function createDataSourceConfig(
data: InfraDataSourceConfigApi.DataSourceConfig,
) {
return requestClient.post('/infra/data-source-config/create', data);
}
/** 修改数据源配置 */
export function updateDataSourceConfig(
data: InfraDataSourceConfigApi.DataSourceConfig,
) {
return requestClient.put('/infra/data-source-config/update', data);
}
/** 删除数据源配置 */
export function deleteDataSourceConfig(id: number) {
return requestClient.delete(`/infra/data-source-config/delete?id=${id}`);
}

View File

@ -0,0 +1,52 @@
import type { Dayjs } from 'dayjs';
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace Demo01ContactApi {
/** 示例联系人信息 */
export interface Demo01Contact {
id: number; // 编号
name?: string; // 名字
sex?: boolean; // 性别
birthday?: Dayjs | string; // 出生年
description?: string; // 简介
avatar: string; // 头像
}
}
/** 查询示例联系人分页 */
export function getDemo01ContactPage(params: PageParam) {
return requestClient.get<PageResult<Demo01ContactApi.Demo01Contact>>(
'/infra/demo01-contact/page',
{ params },
);
}
/** 查询示例联系人详情 */
export function getDemo01Contact(id: number) {
return requestClient.get<Demo01ContactApi.Demo01Contact>(
`/infra/demo01-contact/get?id=${id}`,
);
}
/** 新增示例联系人 */
export function createDemo01Contact(data: Demo01ContactApi.Demo01Contact) {
return requestClient.post('/infra/demo01-contact/create', data);
}
/** 修改示例联系人 */
export function updateDemo01Contact(data: Demo01ContactApi.Demo01Contact) {
return requestClient.put('/infra/demo01-contact/update', data);
}
/** 删除示例联系人 */
export function deleteDemo01Contact(id: number) {
return requestClient.delete(`/infra/demo01-contact/delete?id=${id}`);
}
/** 导出示例联系人 */
export function exportDemo01Contact(params: any) {
return requestClient.download('/infra/demo01-contact/export-excel', params);
}

View File

@ -0,0 +1,46 @@
import { requestClient } from '#/api/request';
export namespace Demo02CategoryApi {
/** 示例分类信息 */
export interface Demo02Category {
id: number; // 编号
name?: string; // 名字
parentId?: number; // 父级编号
children?: Demo02Category[];
}
}
/** 查询示例分类列表 */
export function getDemo02CategoryList(params: any) {
return requestClient.get<Demo02CategoryApi.Demo02Category[]>(
'/infra/demo02-category/list',
{ params },
);
}
/** 查询示例分类详情 */
export function getDemo02Category(id: number) {
return requestClient.get<Demo02CategoryApi.Demo02Category>(
`/infra/demo02-category/get?id=${id}`,
);
}
/** 新增示例分类 */
export function createDemo02Category(data: Demo02CategoryApi.Demo02Category) {
return requestClient.post('/infra/demo02-category/create', data);
}
/** 修改示例分类 */
export function updateDemo02Category(data: Demo02CategoryApi.Demo02Category) {
return requestClient.put('/infra/demo02-category/update', data);
}
/** 删除示例分类 */
export function deleteDemo02Category(id: number) {
return requestClient.delete(`/infra/demo02-category/delete?id=${id}`);
}
/** 导出示例分类 */
export function exportDemo02Category(params: any) {
return requestClient.download('/infra/demo02-category/export-excel', params);
}

View File

@ -0,0 +1,137 @@
import type { Dayjs } from 'dayjs';
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace Demo03StudentApi {
/** 学生课程信息 */
export interface Demo03Course {
id: number; // 编号
studentId?: number; // 学生编号
name?: string; // 名字
score?: number; // 分数
}
/** 学生班级信息 */
export interface Demo03Grade {
id: number; // 编号
studentId?: number; // 学生编号
name?: string; // 名字
teacher?: string; // 班主任
}
/** 学生信息 */
export interface Demo03Student {
id: number; // 编号
name?: string; // 名字
sex?: number; // 性别
birthday?: Dayjs | string; // 出生日期
description?: string; // 简介
}
}
/** 查询学生分页 */
export function getDemo03StudentPage(params: PageParam) {
return requestClient.get<PageResult<Demo03StudentApi.Demo03Student>>(
'/infra/demo03-student/page',
{ params },
);
}
/** 查询学生详情 */
export function getDemo03Student(id: number) {
return requestClient.get<Demo03StudentApi.Demo03Student>(
`/infra/demo03-student/get?id=${id}`,
);
}
/** 新增学生 */
export function createDemo03Student(data: Demo03StudentApi.Demo03Student) {
return requestClient.post('/infra/demo03-student/create', data);
}
/** 修改学生 */
export function updateDemo03Student(data: Demo03StudentApi.Demo03Student) {
return requestClient.put('/infra/demo03-student/update', data);
}
/** 删除学生 */
export function deleteDemo03Student(id: number) {
return requestClient.delete(`/infra/demo03-student/delete?id=${id}`);
}
/** 导出学生 */
export function exportDemo03Student(params: any) {
return requestClient.download('/infra/demo03-student/export-excel', params);
}
// ==================== 子表(学生课程) ====================
/** 获得学生课程分页 */
export function getDemo03CoursePage(params: PageParam) {
return requestClient.get<PageResult<Demo03StudentApi.Demo03Course>>(
`/infra/demo03-student/demo03-course/page`,
{
params,
},
);
}
/** 新增学生课程 */
export function createDemo03Course(data: Demo03StudentApi.Demo03Course) {
return requestClient.post(`/infra/demo03-student/demo03-course/create`, data);
}
/** 修改学生课程 */
export function updateDemo03Course(data: Demo03StudentApi.Demo03Course) {
return requestClient.put(`/infra/demo03-student/demo03-course/update`, data);
}
/** 删除学生课程 */
export function deleteDemo03Course(id: number) {
return requestClient.delete(
`/infra/demo03-student/demo03-course/delete?id=${id}`,
);
}
/** 获得学生课程 */
export function getDemo03Course(id: number) {
return requestClient.get<Demo03StudentApi.Demo03Course>(
`/infra/demo03-student/demo03-course/get?id=${id}`,
);
}
// ==================== 子表(学生班级) ====================
/** 获得学生班级分页 */
export function getDemo03GradePage(params: PageParam) {
return requestClient.get<PageResult<Demo03StudentApi.Demo03Grade>>(
`/infra/demo03-student/demo03-grade/page`,
{
params,
},
);
}
/** 新增学生班级 */
export function createDemo03Grade(data: Demo03StudentApi.Demo03Grade) {
return requestClient.post(`/infra/demo03-student/demo03-grade/create`, data);
}
/** 修改学生班级 */
export function updateDemo03Grade(data: Demo03StudentApi.Demo03Grade) {
return requestClient.put(`/infra/demo03-student/demo03-grade/update`, data);
}
/** 删除学生班级 */
export function deleteDemo03Grade(id: number) {
return requestClient.delete(
`/infra/demo03-student/demo03-grade/delete?id=${id}`,
);
}
/** 获得学生班级 */
export function getDemo03Grade(id: number) {
return requestClient.get<Demo03StudentApi.Demo03Grade>(
`/infra/demo03-student/demo03-grade/get?id=${id}`,
);
}

View File

@ -0,0 +1,85 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace Demo03StudentApi {
/** 学生课程信息 */
export interface Demo03Course {
id: number; // 编号
studentId?: number; // 学生编号
name?: string; // 名字
score?: number; // 分数
}
/** 学生班级信息 */
export interface Demo03Grade {
id: number; // 编号
studentId?: number; // 学生编号
name?: string; // 名字
teacher?: string; // 班主任
}
/** 学生信息 */
export interface Demo03Student {
id: number; // 编号
name?: string; // 名字
sex?: number; // 性别
birthday?: Date; // 出生日期
description?: string; // 简介
demo03courses?: Demo03Course[];
demo03grade?: Demo03Grade;
}
}
/** 查询学生分页 */
export function getDemo03StudentPage(params: PageParam) {
return requestClient.get<PageResult<Demo03StudentApi.Demo03Student>>(
'/infra/demo03-student/page',
{ params },
);
}
/** 查询学生详情 */
export function getDemo03Student(id: number) {
return requestClient.get<Demo03StudentApi.Demo03Student>(
`/infra/demo03-student/get?id=${id}`,
);
}
/** 新增学生 */
export function createDemo03Student(data: Demo03StudentApi.Demo03Student) {
return requestClient.post('/infra/demo03-student/create', data);
}
/** 修改学生 */
export function updateDemo03Student(data: Demo03StudentApi.Demo03Student) {
return requestClient.put('/infra/demo03-student/update', data);
}
/** 删除学生 */
export function deleteDemo03Student(id: number) {
return requestClient.delete(`/infra/demo03-student/delete?id=${id}`);
}
/** 导出学生 */
export function exportDemo03Student(params: any) {
return requestClient.download('/infra/demo03-student/export-excel', params);
}
// ==================== 子表(学生课程) ====================
/** 获得学生课程列表 */
export function getDemo03CourseListByStudentId(studentId: number) {
return requestClient.get<Demo03StudentApi.Demo03Course[]>(
`/infra/demo03-student/demo03-course/list-by-student-id?studentId=${studentId}`,
);
}
// ==================== 子表(学生班级) ====================
/** 获得学生班级 */
export function getDemo03GradeByStudentId(studentId: number) {
return requestClient.get<Demo03StudentApi.Demo03Grade>(
`/infra/demo03-student/demo03-grade/get-by-student-id?studentId=${studentId}`,
);
}

View File

@ -0,0 +1,87 @@
import type { Dayjs } from 'dayjs';
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace Demo03StudentApi {
/** 学生课程信息 */
export interface Demo03Course {
id: number; // 编号
studentId?: number; // 学生编号
name?: string; // 名字
score?: number; // 分数
}
/** 学生班级信息 */
export interface Demo03Grade {
id: number; // 编号
studentId?: number; // 学生编号
name?: string; // 名字
teacher?: string; // 班主任
}
/** 学生信息 */
export interface Demo03Student {
id: number; // 编号
name?: string; // 名字
sex?: number; // 性别
birthday?: Dayjs | string; // 出生日期
description?: string; // 简介
demo03courses?: Demo03Course[];
demo03grade?: Demo03Grade;
}
}
/** 查询学生分页 */
export function getDemo03StudentPage(params: PageParam) {
return requestClient.get<PageResult<Demo03StudentApi.Demo03Student>>(
'/infra/demo03-student/page',
{ params },
);
}
/** 查询学生详情 */
export function getDemo03Student(id: number) {
return requestClient.get<Demo03StudentApi.Demo03Student>(
`/infra/demo03-student/get?id=${id}`,
);
}
/** 新增学生 */
export function createDemo03Student(data: Demo03StudentApi.Demo03Student) {
return requestClient.post('/infra/demo03-student/create', data);
}
/** 修改学生 */
export function updateDemo03Student(data: Demo03StudentApi.Demo03Student) {
return requestClient.put('/infra/demo03-student/update', data);
}
/** 删除学生 */
export function deleteDemo03Student(id: number) {
return requestClient.delete(`/infra/demo03-student/delete?id=${id}`);
}
/** 导出学生 */
export function exportDemo03Student(params: any) {
return requestClient.download('/infra/demo03-student/export-excel', params);
}
// ==================== 子表(学生课程) ====================
/** 获得学生课程列表 */
export function getDemo03CourseListByStudentId(studentId: number) {
return requestClient.get<Demo03StudentApi.Demo03Course[]>(
`/infra/demo03-student/demo03-course/list-by-student-id?studentId=${studentId}`,
);
}
// ==================== 子表(学生班级) ====================
/** 获得学生班级 */
export function getDemo03GradeByStudentId(studentId: number) {
return requestClient.get<Demo03StudentApi.Demo03Grade>(
`/infra/demo03-student/demo03-grade/get-by-student-id?studentId=${studentId}`,
);
}

View File

@ -0,0 +1,75 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace InfraFileConfigApi {
/** 文件客户端配置 */
export interface FileClientConfig {
basePath: string;
host?: string;
port?: number;
username?: string;
password?: string;
mode?: string;
endpoint?: string;
bucket?: string;
accessKey?: string;
accessSecret?: string;
pathStyle?: boolean;
domain: string;
}
/** 文件配置信息 */
export interface FileConfig {
id?: number;
name: string;
storage?: number;
master: boolean;
visible: boolean;
config: FileClientConfig;
remark: string;
createTime?: Date;
}
}
/** 查询文件配置列表 */
export function getFileConfigPage(params: PageParam) {
return requestClient.get<PageResult<InfraFileConfigApi.FileConfig>>(
'/infra/file-config/page',
{
params,
},
);
}
/** 查询文件配置详情 */
export function getFileConfig(id: number) {
return requestClient.get<InfraFileConfigApi.FileConfig>(
`/infra/file-config/get?id=${id}`,
);
}
/** 更新文件配置为主配置 */
export function updateFileConfigMaster(id: number) {
return requestClient.put(`/infra/file-config/update-master?id=${id}`);
}
/** 新增文件配置 */
export function createFileConfig(data: InfraFileConfigApi.FileConfig) {
return requestClient.post('/infra/file-config/create', data);
}
/** 修改文件配置 */
export function updateFileConfig(data: InfraFileConfigApi.FileConfig) {
return requestClient.put('/infra/file-config/update', data);
}
/** 删除文件配置 */
export function deleteFileConfig(id: number) {
return requestClient.delete(`/infra/file-config/delete?id=${id}`);
}
/** 测试文件配置 */
export function testFileConfig(id: number) {
return requestClient.get(`/infra/file-config/test?id=${id}`);
}

View File

@ -0,0 +1,73 @@
import type { AxiosRequestConfig, PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
/** Axios 上传进度事件 */
export type AxiosProgressEvent = AxiosRequestConfig['onUploadProgress'];
export namespace InfraFileApi {
/** 文件信息 */
export interface File {
id?: number;
configId?: number;
path: string;
name?: string;
url?: string;
size?: number;
type?: string;
createTime?: Date;
}
/** 文件预签名地址 */
export interface FilePresignedUrlRespVO {
configId: number; // 文件配置编号
uploadUrl: string; // 文件上传 URL
url: string; // 文件 URL
path: string; // 文件路径
}
/** 上传文件 */
export interface FileUploadReqVO {
file: globalThis.File;
directory?: string;
}
}
/** 查询文件列表 */
export function getFilePage(params: PageParam) {
return requestClient.get<PageResult<InfraFileApi.File>>('/infra/file/page', {
params,
});
}
/** 删除文件 */
export function deleteFile(id: number) {
return requestClient.delete(`/infra/file/delete?id=${id}`);
}
/** 获取文件预签名地址 */
export function getFilePresignedUrl(name: string, directory?: string) {
return requestClient.get<InfraFileApi.FilePresignedUrlRespVO>(
'/infra/file/presigned-url',
{
params: { name, directory },
},
);
}
/** 创建文件 */
export function createFile(data: InfraFileApi.File) {
return requestClient.post('/infra/file/create', data);
}
/** 上传文件 */
export function uploadFile(
data: InfraFileApi.FileUploadReqVO,
onUploadProgress?: AxiosProgressEvent,
) {
// 特殊:由于 upload 内部封装,即使 directory 为 undefined也会传递给后端
if (!data.directory) {
delete data.directory;
}
return requestClient.upload('/infra/file/upload', data, { onUploadProgress });
}

View File

@ -0,0 +1,41 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace InfraJobLogApi {
/** 任务日志信息 */
export interface JobLog {
id?: number;
jobId: number;
handlerName: string;
handlerParam: string;
cronExpression: string;
executeIndex: string;
beginTime: Date;
endTime: Date;
duration: string;
status: number;
createTime?: string;
result: string;
}
}
/** 查询任务日志列表 */
export function getJobLogPage(params: PageParam) {
return requestClient.get<PageResult<InfraJobLogApi.JobLog>>(
'/infra/job-log/page',
{ params },
);
}
/** 查询任务日志详情 */
export function getJobLog(id: number) {
return requestClient.get<InfraJobLogApi.JobLog>(
`/infra/job-log/get?id=${id}`,
);
}
/** 导出定时任务日志 */
export function exportJobLog(params: any) {
return requestClient.download('/infra/job-log/export-excel', { params });
}

View File

@ -0,0 +1,70 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace InfraJobApi {
/** 任务信息 */
export interface Job {
id?: number;
name: string;
status: number;
handlerName: string;
handlerParam: string;
cronExpression: string;
retryCount: number;
retryInterval: number;
monitorTimeout: number;
createTime?: Date;
}
}
/** 查询任务列表 */
export function getJobPage(params: PageParam) {
return requestClient.get<PageResult<InfraJobApi.Job>>('/infra/job/page', {
params,
});
}
/** 查询任务详情 */
export function getJob(id: number) {
return requestClient.get<InfraJobApi.Job>(`/infra/job/get?id=${id}`);
}
/** 新增任务 */
export function createJob(data: InfraJobApi.Job) {
return requestClient.post('/infra/job/create', data);
}
/** 修改定时任务调度 */
export function updateJob(data: InfraJobApi.Job) {
return requestClient.put('/infra/job/update', data);
}
/** 删除定时任务调度 */
export function deleteJob(id: number) {
return requestClient.delete(`/infra/job/delete?id=${id}`);
}
/** 导出定时任务调度 */
export function exportJob(params: any) {
return requestClient.download('/infra/job/export-excel', { params });
}
/** 任务状态修改 */
export function updateJobStatus(id: number, status: number) {
const params = {
id,
status,
};
return requestClient.put('/infra/job/update-status', { params });
}
/** 定时任务立即执行一次 */
export function runJob(id: number) {
return requestClient.put(`/infra/job/trigger?id=${id}`);
}
/** 获得定时任务的下 n 次执行时间 */
export function getJobNextTimes(id: number) {
return requestClient.get(`/infra/job/get_next_times?id=${id}`);
}

View File

@ -0,0 +1,190 @@
import { requestClient } from '#/api/request';
export namespace InfraRedisApi {
/** Redis 信息 */
export interface RedisInfo {
io_threaded_reads_processed: string;
tracking_clients: string;
uptime_in_seconds: string;
cluster_connections: string;
current_cow_size: string;
maxmemory_human: string;
aof_last_cow_size: string;
master_replid2: string;
mem_replication_backlog: string;
aof_rewrite_scheduled: string;
total_net_input_bytes: string;
rss_overhead_ratio: string;
hz: string;
current_cow_size_age: string;
redis_build_id: string;
errorstat_BUSYGROUP: string;
aof_last_bgrewrite_status: string;
multiplexing_api: string;
client_recent_max_output_buffer: string;
allocator_resident: string;
mem_fragmentation_bytes: string;
aof_current_size: string;
repl_backlog_first_byte_offset: string;
tracking_total_prefixes: string;
redis_mode: string;
redis_git_dirty: string;
aof_delayed_fsync: string;
allocator_rss_bytes: string;
repl_backlog_histlen: string;
io_threads_active: string;
rss_overhead_bytes: string;
total_system_memory: string;
loading: string;
evicted_keys: string;
maxclients: string;
cluster_enabled: string;
redis_version: string;
repl_backlog_active: string;
mem_aof_buffer: string;
allocator_frag_bytes: string;
io_threaded_writes_processed: string;
instantaneous_ops_per_sec: string;
used_memory_human: string;
total_error_replies: string;
role: string;
maxmemory: string;
used_memory_lua: string;
rdb_current_bgsave_time_sec: string;
used_memory_startup: string;
used_cpu_sys_main_thread: string;
lazyfree_pending_objects: string;
aof_pending_bio_fsync: string;
used_memory_dataset_perc: string;
allocator_frag_ratio: string;
arch_bits: string;
used_cpu_user_main_thread: string;
mem_clients_normal: string;
expired_time_cap_reached_count: string;
unexpected_error_replies: string;
mem_fragmentation_ratio: string;
aof_last_rewrite_time_sec: string;
master_replid: string;
aof_rewrite_in_progress: string;
lru_clock: string;
maxmemory_policy: string;
run_id: string;
latest_fork_usec: string;
tracking_total_items: string;
total_commands_processed: string;
expired_keys: string;
errorstat_ERR: string;
used_memory: string;
module_fork_in_progress: string;
errorstat_WRONGPASS: string;
aof_buffer_length: string;
dump_payload_sanitizations: string;
mem_clients_slaves: string;
keyspace_misses: string;
server_time_usec: string;
executable: string;
lazyfreed_objects: string;
db0: string;
used_memory_peak_human: string;
keyspace_hits: string;
rdb_last_cow_size: string;
aof_pending_rewrite: string;
used_memory_overhead: string;
active_defrag_hits: string;
tcp_port: string;
uptime_in_days: string;
used_memory_peak_perc: string;
current_save_keys_processed: string;
blocked_clients: string;
total_reads_processed: string;
expire_cycle_cpu_milliseconds: string;
sync_partial_err: string;
used_memory_scripts_human: string;
aof_current_rewrite_time_sec: string;
aof_enabled: string;
process_supervised: string;
master_repl_offset: string;
used_memory_dataset: string;
used_cpu_user: string;
rdb_last_bgsave_status: string;
tracking_total_keys: string;
atomicvar_api: string;
allocator_rss_ratio: string;
client_recent_max_input_buffer: string;
clients_in_timeout_table: string;
aof_last_write_status: string;
mem_allocator: string;
used_memory_scripts: string;
used_memory_peak: string;
process_id: string;
master_failover_state: string;
errorstat_NOAUTH: string;
used_cpu_sys: string;
repl_backlog_size: string;
connected_slaves: string;
current_save_keys_total: string;
gcc_version: string;
total_system_memory_human: string;
sync_full: string;
connected_clients: string;
module_fork_last_cow_size: string;
total_writes_processed: string;
allocator_active: string;
total_net_output_bytes: string;
pubsub_channels: string;
current_fork_perc: string;
active_defrag_key_hits: string;
rdb_changes_since_last_save: string;
instantaneous_input_kbps: string;
used_memory_rss_human: string;
configured_hz: string;
expired_stale_perc: string;
active_defrag_misses: string;
used_cpu_sys_children: string;
number_of_cached_scripts: string;
sync_partial_ok: string;
used_memory_lua_human: string;
rdb_last_save_time: string;
pubsub_patterns: string;
slave_expires_tracked_keys: string;
redis_git_sha1: string;
used_memory_rss: string;
rdb_last_bgsave_time_sec: string;
os: string;
mem_not_counted_for_evict: string;
active_defrag_running: string;
rejected_connections: string;
aof_rewrite_buffer_length: string;
total_forks: string;
active_defrag_key_misses: string;
allocator_allocated: string;
aof_base_size: string;
instantaneous_output_kbps: string;
second_repl_offset: string;
rdb_bgsave_in_progress: string;
used_cpu_user_children: string;
total_connections_received: string;
migrate_cached_sockets: string;
}
/** Redis 命令统计 */
export interface RedisCommandStats {
command: string;
calls: number;
usec: number;
}
/** Redis 监控信息 */
export interface RedisMonitorInfo {
info: RedisInfo;
dbSize: number;
commandStats: RedisCommandStats[];
}
}
/** 获取 Redis 监控信息 */
export function getRedisMonitorInfo() {
return requestClient.get<InfraRedisApi.RedisMonitorInfo>(
'/infra/redis/get-monitor-info',
);
}

View File

@ -3,7 +3,7 @@
*/
import type { RequestClientOptions } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
import { isTenantEnable, useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences';
import {
authenticateResponseInterceptor,
@ -20,6 +20,7 @@ import { useAuthStore } from '#/store';
import { refreshTokenApi } from './core';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
const tenantEnable = isTenantEnable();
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
const client = new RequestClient({
@ -50,8 +51,16 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
*/
async function doRefreshToken() {
const accessStore = useAccessStore();
const resp = await refreshTokenApi();
const newToken = resp.data;
const refreshToken = accessStore.refreshToken as string;
if (!refreshToken) {
throw new Error('Refresh token is null!');
}
const resp = await refreshTokenApi(refreshToken);
const newToken = resp?.data?.data?.accessToken;
// add by 芋艿:这里一定要抛出 resp.data从而触发 authenticateResponseInterceptor 中,刷新令牌失败!!!
if (!newToken) {
throw resp.data;
}
accessStore.setAccessToken(newToken);
return newToken;
}
@ -67,6 +76,14 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
config.headers.Authorization = formatToken(accessStore.accessToken);
config.headers['Accept-Language'] = preferences.app.locale;
// 添加租户编号
config.headers['tenant-id'] = tenantEnable
? accessStore.tenantId
: undefined;
// 只有登录时,才设置 visit-tenant-id 访问租户
config.headers['visit-tenant-id'] = tenantEnable
? accessStore.visitTenantId
: undefined;
return config;
},
});
@ -97,7 +114,12 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
// 当前mock接口返回的错误字段是 error 或者 message
const responseData = error?.response?.data ?? {};
const errorMessage = responseData?.error ?? responseData?.message ?? '';
const errorMessage =
responseData?.error ?? responseData?.message ?? responseData.msg ?? '';
// add by 芋艿:特殊:避免 401 “账号未登录”,重复提示。因为,此时会跳转到登录界面,只需提示一次!!!
if (error?.data?.code === 401) {
return;
}
// 如果没有错误信息,则会根据状态码进行提示
ElMessage.error(errorMessage || msg);
}),
@ -111,3 +133,17 @@ export const requestClient = createRequestClient(apiURL, {
});
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
baseRequestClient.addRequestInterceptor({
fulfilled: (config) => {
const accessStore = useAccessStore();
// 添加租户编号
config.headers['tenant-id'] = tenantEnable
? accessStore.tenantId
: undefined;
// 只有登录时,才设置 visit-tenant-id 访问租户
config.headers['visit-tenant-id'] = tenantEnable
? accessStore.visitTenantId
: undefined;
return config;
},
});

View File

@ -0,0 +1,24 @@
import { requestClient } from '#/api/request';
export namespace SystemAreaApi {
/** 地区信息 */
export interface Area {
id?: number;
name: string;
code: string;
parentId?: number;
sort?: number;
status?: number;
createTime?: Date;
}
}
/** 获得地区树 */
export function getAreaTree() {
return requestClient.get<SystemAreaApi.Area[]>('/system/area/tree');
}
/** 获得 IP 对应的地区名 */
export function getAreaByIp(ip: string) {
return requestClient.get<string>(`/system/area/get-by-ip?ip=${ip}`);
}

View File

@ -0,0 +1,47 @@
import { requestClient } from '#/api/request';
export namespace SystemDeptApi {
/** 部门信息 */
export interface Dept {
id?: number;
name: string;
parentId?: number;
status: number;
sort: number;
leaderUserId: number;
phone: string;
email: string;
createTime: Date;
children?: Dept[];
}
}
/** 查询部门(精简)列表 */
export async function getSimpleDeptList() {
return requestClient.get<SystemDeptApi.Dept[]>('/system/dept/simple-list');
}
/** 查询部门列表 */
export async function getDeptList() {
return requestClient.get('/system/dept/list');
}
/** 查询部门详情 */
export async function getDept(id: number) {
return requestClient.get<SystemDeptApi.Dept>(`/system/dept/get?id=${id}`);
}
/** 新增部门 */
export async function createDept(data: SystemDeptApi.Dept) {
return requestClient.post('/system/dept/create', data);
}
/** 修改部门 */
export async function updateDept(data: SystemDeptApi.Dept) {
return requestClient.put('/system/dept/update', data);
}
/** 删除部门 */
export async function deleteDept(id: number) {
return requestClient.delete(`/system/dept/delete?id=${id}`);
}

View File

@ -0,0 +1,54 @@
import type { PageParam } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemDictDataApi {
/** 字典数据 */
export type DictData = {
colorType: string;
createTime: Date;
cssClass: string;
dictType: string;
id?: number;
label: string;
remark: string;
sort?: number;
status: number;
value: string;
};
}
// 查询字典数据(精简)列表
export function getSimpleDictDataList() {
return requestClient.get('/system/dict-data/simple-list');
}
// 查询字典数据列表
export function getDictDataPage(params: PageParam) {
return requestClient.get('/system/dict-data/page', { params });
}
// 查询字典数据详情
export function getDictData(id: number) {
return requestClient.get(`/system/dict-data/get?id=${id}`);
}
// 新增字典数据
export function createDictData(data: SystemDictDataApi.DictData) {
return requestClient.post('/system/dict-data/create', data);
}
// 修改字典数据
export function updateDictData(data: SystemDictDataApi.DictData) {
return requestClient.put('/system/dict-data/update', data);
}
// 删除字典数据
export function deleteDictData(id: number) {
return requestClient.delete(`/system/dict-data/delete?id=${id}`);
}
// 导出字典类型数据
export function exportDictData(params: any) {
return requestClient.download('/system/dict-data/export', { params });
}

View File

@ -0,0 +1,48 @@
import { requestClient } from '#/api/request';
export namespace SystemDictTypeApi {
/** 字典类型 */
export type DictType = {
createTime: Date;
id?: number;
name: string;
remark: string;
status: number;
type: string;
};
}
// 查询字典(精简)列表
export function getSimpleDictTypeList() {
return requestClient.get('/system/dict-type/list-all-simple');
}
// 查询字典列表
export function getDictTypePage(params: any) {
return requestClient.get('/system/dict-type/page', { params });
}
// 查询字典详情
export function getDictType(id: number) {
return requestClient.get(`/system/dict-type/get?id=${id}`);
}
// 新增字典
export function createDictType(data: SystemDictTypeApi.DictType) {
return requestClient.post('/system/dict-type/create', data);
}
// 修改字典
export function updateDictType(data: SystemDictTypeApi.DictType) {
return requestClient.put('/system/dict-type/update', data);
}
// 删除字典
export function deleteDictType(id: number) {
return requestClient.delete(`/system/dict-type/delete?id=${id}`);
}
// 导出字典类型
export function exportDictType(params: any) {
return requestClient.download('/system/dict-type/export', { params });
}

View File

@ -0,0 +1,33 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemLoginLogApi {
/** 登录日志信息 */
export interface LoginLog {
id: number;
logType: number;
traceId: number;
userId: number;
userType: number;
username: string;
result: number;
status: number;
userIp: string;
userAgent: string;
createTime: string;
}
}
/** 查询登录日志列表 */
export function getLoginLogPage(params: PageParam) {
return requestClient.get<PageResult<SystemLoginLogApi.LoginLog>>(
'/system/login-log/page',
{ params },
);
}
/** 导出登录日志 */
export function exportLoginLog(params: any) {
return requestClient.download('/system/login-log/export-excel', { params });
}

View File

@ -0,0 +1,57 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemMailAccountApi {
/** 邮箱账号 */
export interface MailAccount {
id: number;
mail: string;
username: string;
password: string;
host: string;
port: number;
sslEnable: boolean;
starttlsEnable: boolean;
status: number;
createTime: Date;
remark: string;
}
}
/** 查询邮箱账号列表 */
export function getMailAccountPage(params: PageParam) {
return requestClient.get<PageResult<SystemMailAccountApi.MailAccount>>(
'/system/mail-account/page',
{ params },
);
}
/** 查询邮箱账号详情 */
export function getMailAccount(id: number) {
return requestClient.get<SystemMailAccountApi.MailAccount>(
`/system/mail-account/get?id=${id}`,
);
}
/** 新增邮箱账号 */
export function createMailAccount(data: SystemMailAccountApi.MailAccount) {
return requestClient.post('/system/mail-account/create', data);
}
/** 修改邮箱账号 */
export function updateMailAccount(data: SystemMailAccountApi.MailAccount) {
return requestClient.put('/system/mail-account/update', data);
}
/** 删除邮箱账号 */
export function deleteMailAccount(id: number) {
return requestClient.delete(`/system/mail-account/delete?id=${id}`);
}
/** 获得邮箱账号精简列表 */
export function getSimpleMailAccountList() {
return requestClient.get<SystemMailAccountApi.MailAccount[]>(
'/system/mail-account/simple-list',
);
}

View File

@ -0,0 +1,46 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemMailLogApi {
/** 邮件日志 */
export interface MailLog {
id: number;
userId: number;
userType: number;
toMail: string;
accountId: number;
fromMail: string;
templateId: number;
templateCode: string;
templateNickname: string;
templateTitle: string;
templateContent: string;
templateParams: string;
sendStatus: number;
sendTime: string;
sendMessageId: string;
sendException: string;
createTime: string;
}
}
/** 查询邮件日志列表 */
export function getMailLogPage(params: PageParam) {
return requestClient.get<PageResult<SystemMailLogApi.MailLog>>(
'/system/mail-log/page',
{ params },
);
}
/** 查询邮件日志详情 */
export function getMailLog(id: number) {
return requestClient.get<SystemMailLogApi.MailLog>(
`/system/mail-log/get?id=${id}`,
);
}
/** 重新发送邮件 */
export function resendMail(id: number) {
return requestClient.put(`/system/mail-log/resend?id=${id}`);
}

View File

@ -0,0 +1,62 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemMailTemplateApi {
/** 邮件模版信息 */
export interface MailTemplate {
id: number;
name: string;
code: string;
accountId: number;
nickname: string;
title: string;
content: string;
params: string[];
status: number;
remark: string;
createTime: Date;
}
/** 邮件发送信息 */
export interface MailSendReqVO {
mail: string;
templateCode: string;
templateParams: Record<string, any>;
}
}
/** 查询邮件模版列表 */
export function getMailTemplatePage(params: PageParam) {
return requestClient.get<PageResult<SystemMailTemplateApi.MailTemplate>>(
'/system/mail-template/page',
{ params },
);
}
/** 查询邮件模版详情 */
export function getMailTemplate(id: number) {
return requestClient.get<SystemMailTemplateApi.MailTemplate>(
`/system/mail-template/get?id=${id}`,
);
}
/** 新增邮件模版 */
export function createMailTemplate(data: SystemMailTemplateApi.MailTemplate) {
return requestClient.post('/system/mail-template/create', data);
}
/** 修改邮件模版 */
export function updateMailTemplate(data: SystemMailTemplateApi.MailTemplate) {
return requestClient.put('/system/mail-template/update', data);
}
/** 删除邮件模版 */
export function deleteMailTemplate(id: number) {
return requestClient.delete(`/system/mail-template/delete?id=${id}`);
}
/** 发送邮件 */
export function sendMail(data: SystemMailTemplateApi.MailSendReqVO) {
return requestClient.post('/system/mail-template/send-mail', data);
}

View File

@ -0,0 +1,54 @@
import { requestClient } from '#/api/request';
export namespace SystemMenuApi {
/** 菜单信息 */
export interface Menu {
id: number;
name: string;
permission: string;
type: number;
sort: number;
parentId: number;
path: string;
icon: string;
component: string;
componentName?: string;
status: number;
visible: boolean;
keepAlive: boolean;
alwaysShow?: boolean;
createTime: Date;
}
}
/** 查询菜单(精简)列表 */
export async function getSimpleMenusList() {
return requestClient.get<SystemMenuApi.Menu[]>('/system/menu/simple-list');
}
/** 查询菜单列表 */
export async function getMenuList(params?: Record<string, any>) {
return requestClient.get<SystemMenuApi.Menu[]>('/system/menu/list', {
params,
});
}
/** 获取菜单详情 */
export async function getMenu(id: number) {
return requestClient.get<SystemMenuApi.Menu>(`/system/menu/get?id=${id}`);
}
/** 新增菜单 */
export async function createMenu(data: SystemMenuApi.Menu) {
return requestClient.post('/system/menu/create', data);
}
/** 修改菜单 */
export async function updateMenu(data: SystemMenuApi.Menu) {
return requestClient.put('/system/menu/update', data);
}
/** 删除菜单 */
export async function deleteMenu(id: number) {
return requestClient.delete(`/system/menu/delete?id=${id}`);
}

View File

@ -0,0 +1,52 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemNoticeApi {
/** 公告信息 */
export interface Notice {
id?: number;
title: string;
type: number;
content: string;
status: number;
remark: string;
creator?: string;
createTime?: Date;
}
}
/** 查询公告列表 */
export function getNoticePage(params: PageParam) {
return requestClient.get<PageResult<SystemNoticeApi.Notice>>(
'/system/notice/page',
{ params },
);
}
/** 查询公告详情 */
export function getNotice(id: number) {
return requestClient.get<SystemNoticeApi.Notice>(
`/system/notice/get?id=${id}`,
);
}
/** 新增公告 */
export function createNotice(data: SystemNoticeApi.Notice) {
return requestClient.post('/system/notice/create', data);
}
/** 修改公告 */
export function updateNotice(data: SystemNoticeApi.Notice) {
return requestClient.put('/system/notice/update', data);
}
/** 删除公告 */
export function deleteNotice(id: number) {
return requestClient.delete(`/system/notice/delete?id=${id}`);
}
/** 推送公告 */
export function pushNotice(id: number) {
return requestClient.post(`/system/notice/push?id=${id}`);
}

View File

@ -0,0 +1,65 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemNotifyMessageApi {
/** 站内信消息信息 */
export interface NotifyMessage {
id: number;
userId: number;
userType: number;
templateId: number;
templateCode: string;
templateNickname: string;
templateContent: string;
templateType: number;
templateParams: string;
readStatus: boolean;
readTime: Date;
createTime: Date;
}
}
/** 查询站内信消息列表 */
export function getNotifyMessagePage(params: PageParam) {
return requestClient.get<PageResult<SystemNotifyMessageApi.NotifyMessage>>(
'/system/notify-message/page',
{ params },
);
}
/** 获得我的站内信分页 */
export function getMyNotifyMessagePage(params: PageParam) {
return requestClient.get<PageResult<SystemNotifyMessageApi.NotifyMessage>>(
'/system/notify-message/my-page',
{ params },
);
}
/** 批量标记已读 */
export function updateNotifyMessageRead(ids: number[]) {
return requestClient.put(
'/system/notify-message/update-read',
{},
{
params: { ids },
},
);
}
/** 标记所有站内信为已读 */
export function updateAllNotifyMessageRead() {
return requestClient.put('/system/notify-message/update-all-read');
}
/** 获取当前用户的最新站内信列表 */
export function getUnreadNotifyMessageList() {
return requestClient.get<SystemNotifyMessageApi.NotifyMessage[]>(
'/system/notify-message/get-unread-list',
);
}
/** 获得当前用户的未读站内信数量 */
export function getUnreadNotifyMessageCount() {
return requestClient.get<number>('/system/notify-message/get-unread-count');
}

View File

@ -0,0 +1,72 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemNotifyTemplateApi {
/** 站内信模板信息 */
export interface NotifyTemplate {
id?: number;
name: string;
nickname: string;
code: string;
content: string;
type?: number;
params: string[];
status: number;
remark: string;
}
/** 发送站内信请求 */
export interface NotifySendReqVO {
userId: number;
userType: number;
templateCode: string;
templateParams: Record<string, any>;
}
}
/** 查询站内信模板列表 */
export function getNotifyTemplatePage(params: PageParam) {
return requestClient.get<PageResult<SystemNotifyTemplateApi.NotifyTemplate>>(
'/system/notify-template/page',
{ params },
);
}
/** 查询站内信模板详情 */
export function getNotifyTemplate(id: number) {
return requestClient.get<SystemNotifyTemplateApi.NotifyTemplate>(
`/system/notify-template/get?id=${id}`,
);
}
/** 新增站内信模板 */
export function createNotifyTemplate(
data: SystemNotifyTemplateApi.NotifyTemplate,
) {
return requestClient.post('/system/notify-template/create', data);
}
/** 修改站内信模板 */
export function updateNotifyTemplate(
data: SystemNotifyTemplateApi.NotifyTemplate,
) {
return requestClient.put('/system/notify-template/update', data);
}
/** 删除站内信模板 */
export function deleteNotifyTemplate(id: number) {
return requestClient.delete(`/system/notify-template/delete?id=${id}`);
}
/** 导出站内信模板 */
export function exportNotifyTemplate(params: any) {
return requestClient.download('/system/notify-template/export-excel', {
params,
});
}
/** 发送站内信 */
export function sendNotify(data: SystemNotifyTemplateApi.NotifySendReqVO) {
return requestClient.post('/system/notify-template/send-notify', data);
}

View File

@ -0,0 +1,57 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemOAuth2ClientApi {
/** OAuth2.0 客户端信息 */
export interface OAuth2Client {
id?: number;
clientId: string;
secret: string;
name: string;
logo: string;
description: string;
status: number;
accessTokenValiditySeconds: number;
refreshTokenValiditySeconds: number;
redirectUris: string[];
autoApprove: boolean;
authorizedGrantTypes: string[];
scopes: string[];
authorities: string[];
resourceIds: string[];
additionalInformation: string;
isAdditionalInformationJson: boolean;
createTime?: Date;
}
}
/** 查询 OAuth2.0 客户端列表 */
export function getOAuth2ClientPage(params: PageParam) {
return requestClient.get<PageResult<SystemOAuth2ClientApi.OAuth2Client>>(
'/system/oauth2-client/page',
{ params },
);
}
/** 查询 OAuth2.0 客户端详情 */
export function getOAuth2Client(id: number) {
return requestClient.get<SystemOAuth2ClientApi.OAuth2Client>(
`/system/oauth2-client/get?id=${id}`,
);
}
/** 新增 OAuth2.0 客户端 */
export function createOAuth2Client(data: SystemOAuth2ClientApi.OAuth2Client) {
return requestClient.post('/system/oauth2-client/create', data);
}
/** 修改 OAuth2.0 客户端 */
export function updateOAuth2Client(data: SystemOAuth2ClientApi.OAuth2Client) {
return requestClient.put('/system/oauth2-client/update', data);
}
/** 删除 OAuth2.0 客户端 */
export function deleteOAuth2Client(id: number) {
return requestClient.delete(`/system/oauth2-client/delete?id=${id}`);
}

View File

@ -0,0 +1,58 @@
import { requestClient } from '#/api/request';
/** OAuth2.0 授权信息响应 */
export namespace SystemOAuth2ClientApi {
/** 授权信息 */
export interface AuthorizeInfoRespVO {
client: {
logo: string;
name: string;
};
scopes: {
key: string;
value: boolean;
}[];
}
}
/** 获得授权信息 */
export function getAuthorize(clientId: string) {
return requestClient.get<SystemOAuth2ClientApi.AuthorizeInfoRespVO>(
`/system/oauth2/authorize?clientId=${clientId}`,
);
}
/** 发起授权 */
export function authorize(
responseType: string,
clientId: string,
redirectUri: string,
state: string,
autoApprove: boolean,
checkedScopes: string[],
uncheckedScopes: string[],
) {
// 构建 scopes
const scopes: Record<string, boolean> = {};
for (const scope of checkedScopes) {
scopes[scope] = true;
}
for (const scope of uncheckedScopes) {
scopes[scope] = false;
}
// 发起请求
return requestClient.post<string>('/system/oauth2/authorize', null, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
params: {
response_type: responseType,
client_id: clientId,
redirect_uri: redirectUri,
state,
auto_approve: autoApprove,
scope: JSON.stringify(scopes),
},
});
}

View File

@ -0,0 +1,34 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemOAuth2TokenApi {
/** OAuth2.0 令牌信息 */
export interface OAuth2Token {
id?: number;
accessToken: string;
refreshToken: string;
userId: number;
userType: number;
clientId: string;
createTime?: Date;
expiresTime?: Date;
}
}
/** 查询 OAuth2.0 令牌列表 */
export function getOAuth2TokenPage(params: PageParam) {
return requestClient.get<PageResult<SystemOAuth2TokenApi.OAuth2Token>>(
'/system/oauth2-token/page',
{
params,
},
);
}
/** 删除 OAuth2.0 令牌 */
export function deleteOAuth2Token(accessToken: string) {
return requestClient.delete(
`/system/oauth2-token/delete?accessToken=${accessToken}`,
);
}

View File

@ -0,0 +1,39 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemOperateLogApi {
/** 操作日志信息 */
export interface OperateLog {
id: number;
traceId: string;
userType: number;
userId: number;
userName: string;
type: string;
subType: string;
bizId: number;
action: string;
extra: string;
requestMethod: string;
requestUrl: string;
userIp: string;
userAgent: string;
creator: string;
creatorName: string;
createTime: string;
}
}
/** 查询操作日志列表 */
export function getOperateLogPage(params: PageParam) {
return requestClient.get<PageResult<SystemOperateLogApi.OperateLog>>(
'/system/operate-log/page',
{ params },
);
}
/** 导出操作日志 */
export function exportOperateLog(params: any) {
return requestClient.download('/system/operate-log/export-excel', { params });
}

View File

@ -0,0 +1,57 @@
import { requestClient } from '#/api/request';
export namespace SystemPermissionApi {
/** 分配用户角色请求 */
export interface AssignUserRoleReqVO {
userId: number;
roleIds: number[];
}
/** 分配角色菜单请求 */
export interface AssignRoleMenuReqVO {
roleId: number;
menuIds: number[];
}
/** 分配角色数据权限请求 */
export interface AssignRoleDataScopeReqVO {
roleId: number;
dataScope: number;
dataScopeDeptIds: number[];
}
}
/** 查询角色拥有的菜单权限 */
export async function getRoleMenuList(roleId: number) {
return requestClient.get(
`/system/permission/list-role-menus?roleId=${roleId}`,
);
}
/** 赋予角色菜单权限 */
export async function assignRoleMenu(
data: SystemPermissionApi.AssignRoleMenuReqVO,
) {
return requestClient.post('/system/permission/assign-role-menu', data);
}
/** 赋予角色数据权限 */
export async function assignRoleDataScope(
data: SystemPermissionApi.AssignRoleDataScopeReqVO,
) {
return requestClient.post('/system/permission/assign-role-data-scope', data);
}
/** 查询用户拥有的角色数组 */
export async function getUserRoleList(userId: number) {
return requestClient.get(
`/system/permission/list-user-roles?userId=${userId}`,
);
}
/** 赋予用户角色 */
export async function assignUserRole(
data: SystemPermissionApi.AssignUserRoleReqVO,
) {
return requestClient.post('/system/permission/assign-user-role', data);
}

View File

@ -0,0 +1,58 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemPostApi {
/** 岗位信息 */
export interface Post {
id?: number;
name: string;
code: string;
sort: number;
status: number;
remark: string;
createTime?: Date;
}
}
/** 查询岗位列表 */
export function getPostPage(params: PageParam) {
return requestClient.get<PageResult<SystemPostApi.Post>>(
'/system/post/page',
{
params,
},
);
}
/** 获取岗位精简信息列表 */
export function getSimplePostList() {
return requestClient.get<SystemPostApi.Post[]>('/system/post/simple-list');
}
/** 查询岗位详情 */
export function getPost(id: number) {
return requestClient.get<SystemPostApi.Post>(`/system/post/get?id=${id}`);
}
/** 新增岗位 */
export function createPost(data: SystemPostApi.Post) {
return requestClient.post('/system/post/create', data);
}
/** 修改岗位 */
export function updatePost(data: SystemPostApi.Post) {
return requestClient.put('/system/post/update', data);
}
/** 删除岗位 */
export function deletePost(id: number) {
return requestClient.delete(`/system/post/delete?id=${id}`);
}
/** 导出岗位 */
export function exportPost(params: any) {
return requestClient.download('/system/post/export', {
params,
});
}

View File

@ -0,0 +1,58 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemRoleApi {
/** 角色信息 */
export interface Role {
id?: number;
name: string;
code: string;
sort: number;
status: number;
type: number;
dataScope: number;
dataScopeDeptIds: number[];
createTime?: Date;
}
}
/** 查询角色列表 */
export function getRolePage(params: PageParam) {
return requestClient.get<PageResult<SystemRoleApi.Role>>(
'/system/role/page',
{ params },
);
}
/** 查询角色(精简)列表 */
export function getSimpleRoleList() {
return requestClient.get<SystemRoleApi.Role[]>('/system/role/simple-list');
}
/** 查询角色详情 */
export function getRole(id: number) {
return requestClient.get<SystemRoleApi.Role>(`/system/role/get?id=${id}`);
}
/** 新增角色 */
export function createRole(data: SystemRoleApi.Role) {
return requestClient.post('/system/role/create', data);
}
/** 修改角色 */
export function updateRole(data: SystemRoleApi.Role) {
return requestClient.put('/system/role/update', data);
}
/** 删除角色 */
export function deleteRole(id: number) {
return requestClient.delete(`/system/role/delete?id=${id}`);
}
/** 导出角色 */
export function exportRole(params: any) {
return requestClient.download('/system/role/export-excel', {
params,
});
}

View File

@ -0,0 +1,60 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemSmsChannelApi {
/** 短信渠道信息 */
export interface SmsChannel {
id?: number;
code: string;
status: number;
signature: string;
remark: string;
apiKey: string;
apiSecret: string;
callbackUrl: string;
createTime?: Date;
}
}
/** 查询短信渠道列表 */
export function getSmsChannelPage(params: PageParam) {
return requestClient.get<PageResult<SystemSmsChannelApi.SmsChannel>>(
'/system/sms-channel/page',
{ params },
);
}
/** 获得短信渠道精简列表 */
export function getSimpleSmsChannelList() {
return requestClient.get<SystemSmsChannelApi.SmsChannel[]>(
'/system/sms-channel/simple-list',
);
}
/** 查询短信渠道详情 */
export function getSmsChannel(id: number) {
return requestClient.get<SystemSmsChannelApi.SmsChannel>(
`/system/sms-channel/get?id=${id}`,
);
}
/** 新增短信渠道 */
export function createSmsChannel(data: SystemSmsChannelApi.SmsChannel) {
return requestClient.post('/system/sms-channel/create', data);
}
/** 修改短信渠道 */
export function updateSmsChannel(data: SystemSmsChannelApi.SmsChannel) {
return requestClient.put('/system/sms-channel/update', data);
}
/** 删除短信渠道 */
export function deleteSmsChannel(id: number) {
return requestClient.delete(`/system/sms-channel/delete?id=${id}`);
}
/** 导出短信渠道 */
export function exportSmsChannel(params: any) {
return requestClient.download('/system/sms-channel/export', { params });
}

View File

@ -0,0 +1,45 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemSmsLogApi {
/** 短信日志信息 */
export interface SmsLog {
id?: number;
channelId?: number;
channelCode: string;
templateId?: number;
templateCode: string;
templateType?: number;
templateContent: string;
templateParams?: Record<string, any>;
apiTemplateId: string;
mobile: string;
userId?: number;
userType?: number;
sendStatus?: number;
sendTime?: string;
apiSendCode: string;
apiSendMsg: string;
apiRequestId: string;
apiSerialNo: string;
receiveStatus?: number;
receiveTime?: string;
apiReceiveCode: string;
apiReceiveMsg: string;
createTime: string;
}
}
/** 查询短信日志列表 */
export function getSmsLogPage(params: PageParam) {
return requestClient.get<PageResult<SystemSmsLogApi.SmsLog>>(
'/system/sms-log/page',
{ params },
);
}
/** 导出短信日志 */
export function exportSmsLog(params: any) {
return requestClient.download('/system/sms-log/export-excel', { params });
}

View File

@ -0,0 +1,70 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemSmsTemplateApi {
/** 短信模板信息 */
export interface SmsTemplate {
id?: number;
type?: number;
status: number;
code: string;
name: string;
content: string;
remark: string;
apiTemplateId: string;
channelId?: number;
channelCode?: string;
params?: string[];
createTime?: Date;
}
/** 发送短信请求 */
export interface SmsSendReqVO {
mobile: string;
templateCode: string;
templateParams: Record<string, any>;
}
}
/** 查询短信模板列表 */
export function getSmsTemplatePage(params: PageParam) {
return requestClient.get<PageResult<SystemSmsTemplateApi.SmsTemplate>>(
'/system/sms-template/page',
{ params },
);
}
/** 查询短信模板详情 */
export function getSmsTemplate(id: number) {
return requestClient.get<SystemSmsTemplateApi.SmsTemplate>(
`/system/sms-template/get?id=${id}`,
);
}
/** 新增短信模板 */
export function createSmsTemplate(data: SystemSmsTemplateApi.SmsTemplate) {
return requestClient.post('/system/sms-template/create', data);
}
/** 修改短信模板 */
export function updateSmsTemplate(data: SystemSmsTemplateApi.SmsTemplate) {
return requestClient.put('/system/sms-template/update', data);
}
/** 删除短信模板 */
export function deleteSmsTemplate(id: number) {
return requestClient.delete(`/system/sms-template/delete?id=${id}`);
}
/** 导出短信模板 */
export function exportSmsTemplate(params: any) {
return requestClient.download('/system/sms-template/export-excel', {
params,
});
}
/** 发送短信 */
export function sendSms(data: SystemSmsTemplateApi.SmsSendReqVO) {
return requestClient.post('/system/sms-template/send-sms', data);
}

View File

@ -0,0 +1,48 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemSocialClientApi {
/** 社交客户端信息 */
export interface SocialClient {
id?: number;
name: string;
socialType: number;
userType: number;
clientId: string;
clientSecret: string;
agentId?: string;
status: number;
createTime?: Date;
}
}
/** 查询社交客户端列表 */
export function getSocialClientPage(params: PageParam) {
return requestClient.get<PageResult<SystemSocialClientApi.SocialClient>>(
'/system/social-client/page',
{ params },
);
}
/** 查询社交客户端详情 */
export function getSocialClient(id: number) {
return requestClient.get<SystemSocialClientApi.SocialClient>(
`/system/social-client/get?id=${id}`,
);
}
/** 新增社交客户端 */
export function createSocialClient(data: SystemSocialClientApi.SocialClient) {
return requestClient.post('/system/social-client/create', data);
}
/** 修改社交客户端 */
export function updateSocialClient(data: SystemSocialClientApi.SocialClient) {
return requestClient.put('/system/social-client/update', data);
}
/** 删除社交客户端 */
export function deleteSocialClient(id: number) {
return requestClient.delete(`/system/social-client/delete?id=${id}`);
}

View File

@ -0,0 +1,66 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemSocialUserApi {
/** 社交用户信息 */
export interface SocialUser {
id?: number;
type: number;
openid: string;
token: string;
rawTokenInfo: string;
nickname: string;
avatar: string;
rawUserInfo: string;
code: string;
state: string;
createTime?: Date;
updateTime?: Date;
}
/** 社交绑定请求 */
export interface SocialUserBindReqVO {
type: number;
code: string;
state: string;
}
/** 取消社交绑定请求 */
export interface SocialUserUnbindReqVO {
type: number;
openid: string;
}
}
/** 查询社交用户列表 */
export function getSocialUserPage(params: PageParam) {
return requestClient.get<PageResult<SystemSocialUserApi.SocialUser>>(
'/system/social-user/page',
{ params },
);
}
/** 查询社交用户详情 */
export function getSocialUser(id: number) {
return requestClient.get<SystemSocialUserApi.SocialUser>(
`/system/social-user/get?id=${id}`,
);
}
/** 社交绑定,使用 code 授权码 */
export function socialBind(data: SystemSocialUserApi.SocialUserBindReqVO) {
return requestClient.post<boolean>('/system/social-user/bind', data);
}
/** 取消社交绑定 */
export function socialUnbind(data: SystemSocialUserApi.SocialUserUnbindReqVO) {
return requestClient.delete<boolean>('/system/social-user/unbind', { data });
}
/** 获得绑定社交用户列表 */
export function getBindSocialUserList() {
return requestClient.get<SystemSocialUserApi.SocialUser[]>(
'/system/social-user/get-bind-list',
);
}

View File

@ -0,0 +1,57 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemTenantPackageApi {
/** 租户套餐信息 */
export interface TenantPackage {
id: number;
name: string;
status: number;
remark: string;
creator: string;
updater: string;
updateTime: string;
menuIds: number[];
createTime: Date;
}
}
/** 租户套餐列表 */
export function getTenantPackagePage(params: PageParam) {
return requestClient.get<PageResult<SystemTenantPackageApi.TenantPackage>>(
'/system/tenant-package/page',
{ params },
);
}
/** 查询租户套餐详情 */
export function getTenantPackage(id: number) {
return requestClient.get(`/system/tenant-package/get?id=${id}`);
}
/** 新增租户套餐 */
export function createTenantPackage(
data: SystemTenantPackageApi.TenantPackage,
) {
return requestClient.post('/system/tenant-package/create', data);
}
/** 修改租户套餐 */
export function updateTenantPackage(
data: SystemTenantPackageApi.TenantPackage,
) {
return requestClient.put('/system/tenant-package/update', data);
}
/** 删除租户套餐 */
export function deleteTenantPackage(id: number) {
return requestClient.delete(`/system/tenant-package/delete?id=${id}`);
}
/** 获取租户套餐精简信息列表 */
export function getTenantPackageList() {
return requestClient.get<SystemTenantPackageApi.TenantPackage[]>(
'/system/tenant-package/get-simple-list',
);
}

View File

@ -0,0 +1,69 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemTenantApi {
/** 租户信息 */
export interface Tenant {
id?: number;
name: string;
packageId: number;
contactName: string;
contactMobile: string;
accountCount: number;
expireTime: Date;
website: string;
status: number;
}
}
/** 租户列表 */
export function getTenantPage(params: PageParam) {
return requestClient.get<PageResult<SystemTenantApi.Tenant>>(
'/system/tenant/page',
{ params },
);
}
/** 获取租户精简信息列表 */
export function getSimpleTenantList() {
return requestClient.get<SystemTenantApi.Tenant[]>(
'/system/tenant/simple-list',
);
}
/** 查询租户详情 */
export function getTenant(id: number) {
return requestClient.get<SystemTenantApi.Tenant>(
`/system/tenant/get?id=${id}`,
);
}
/** 获取租户精简信息列表 */
export function getTenantList() {
return requestClient.get<SystemTenantApi.Tenant[]>(
'/system/tenant/simple-list',
);
}
/** 新增租户 */
export function createTenant(data: SystemTenantApi.Tenant) {
return requestClient.post('/system/tenant/create', data);
}
/** 修改租户 */
export function updateTenant(data: SystemTenantApi.Tenant) {
return requestClient.put('/system/tenant/update', data);
}
/** 删除租户 */
export function deleteTenant(id: number) {
return requestClient.delete(`/system/tenant/delete?id=${id}`);
}
/** 导出租户 */
export function exportTenant(params: any) {
return requestClient.download('/system/tenant/export-excel', {
params,
});
}

View File

@ -0,0 +1,83 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace SystemUserApi {
/** 用户信息 */
export interface User {
id?: number;
username: string;
nickname: string;
deptId: number;
postIds: string[];
email: string;
mobile: string;
sex: number;
avatar: string;
loginIp: string;
status: number;
remark: string;
createTime?: Date;
}
}
/** 查询用户管理列表 */
export function getUserPage(params: PageParam) {
return requestClient.get<PageResult<SystemUserApi.User>>(
'/system/user/page',
{ params },
);
}
/** 查询用户详情 */
export function getUser(id: number) {
return requestClient.get<SystemUserApi.User>(`/system/user/get?id=${id}`);
}
/** 新增用户 */
export function createUser(data: SystemUserApi.User) {
return requestClient.post('/system/user/create', data);
}
/** 修改用户 */
export function updateUser(data: SystemUserApi.User) {
return requestClient.put('/system/user/update', data);
}
/** 删除用户 */
export function deleteUser(id: number) {
return requestClient.delete(`/system/user/delete?id=${id}`);
}
/** 导出用户 */
export function exportUser(params: any) {
return requestClient.download('/system/user/export', params);
}
/** 下载用户导入模板 */
export function importUserTemplate() {
return requestClient.download('/system/user/get-import-template');
}
/** 导入用户 */
export function importUser(file: File, updateSupport: boolean) {
return requestClient.upload('/system/user/import', {
file,
updateSupport,
});
}
/** 用户密码重置 */
export function resetUserPassword(id: number, password: string) {
return requestClient.put('/system/user/update-password', { id, password });
}
/** 用户状态修改 */
export function updateUserStatus(id: number, status: number) {
return requestClient.put('/system/user/update-status', { id, status });
}
/** 获取用户精简信息列表 */
export function getSimpleUserList() {
return requestClient.get<SystemUserApi.User[]>('/system/user/simple-list');
}

View File

@ -0,0 +1,56 @@
import { requestClient } from '#/api/request';
export namespace SystemUserProfileApi {
/** 用户个人中心信息 */
export interface UserProfileRespVO {
id: number;
username: string;
nickname: string;
email?: string;
mobile?: string;
sex?: number;
avatar?: string;
loginIp: string;
loginDate: string;
createTime: string;
roles: any[];
dept: any;
posts: any[];
}
/** 更新密码请求 */
export interface UpdatePasswordReqVO {
oldPassword: string;
newPassword: string;
}
/** 更新个人信息请求 */
export interface UpdateProfileReqVO {
nickname?: string;
email?: string;
mobile?: string;
sex?: number;
avatar?: string;
}
}
/** 获取登录用户信息 */
export function getUserProfile() {
return requestClient.get<SystemUserProfileApi.UserProfileRespVO>(
'/system/user/profile/get',
);
}
/** 修改用户个人信息 */
export function updateUserProfile(
data: SystemUserProfileApi.UpdateProfileReqVO,
) {
return requestClient.put('/system/user/profile/update', data);
}
/** 修改用户个人密码 */
export function updateUserPassword(
data: SystemUserProfileApi.UpdatePasswordReqVO,
) {
return requestClient.put('/system/user/profile/update-password', data);
}

View File

@ -0,0 +1,84 @@
<script setup lang="ts">
import { computed } from 'vue';
import { ElTag } from 'element-plus';
// import { isHexColor } from '@/utils/color' // TODO @ cssClass https://gitee.com/yudaocode/yudao-ui-admin-vben/blob/v2.4.1/src/components/DictTag/src/DictTag.vue#L60
import { getDictObj } from '#/utils';
interface DictTagProps {
/**
* 字典类型
*/
type: string;
/**
* 字典值
*/
value: any;
/**
* 图标
*/
icon?: string;
}
const props = defineProps<DictTagProps>();
/** 获取字典标签 */
const dictTag = computed(() => {
const defaultDict = {
label: '',
colorType: 'primary',
};
//
if (!props.type || props.value === undefined || props.value === null) {
return defaultDict;
}
//
const dict = getDictObj(props.type, String(props.value));
if (!dict) {
return defaultDict;
}
//
let colorType = dict.colorType;
switch (colorType) {
case 'danger': {
colorType = 'danger';
break;
}
case 'info': {
colorType = 'info';
break;
}
case 'primary': {
colorType = 'primary';
break;
}
case 'success': {
colorType = 'success';
break;
}
case 'warning': {
colorType = 'warning';
break;
}
default: {
if (!colorType) {
colorType = 'primary';
}
}
}
return {
label: dict.label || '',
colorType,
};
});
</script>
<template>
<ElTag v-if="dictTag" :type="dictTag.colorType">
{{ dictTag.label }}
</ElTag>
</template>

View File

@ -0,0 +1 @@
export { default as DictTag } from './dict-tag.vue';

View File

@ -0,0 +1,37 @@
<script lang="ts" setup>
import { isDocAlertEnable } from '@vben/hooks';
import { openWindow } from '@vben/utils';
import { ElAlert, ElLink } from 'element-plus';
export interface DocAlertProps {
/**
* 文档标题
*/
title: string;
/**
* 文档 URL 地址
*/
url: string;
}
const props = defineProps<DocAlertProps>();
/** 跳转 URL 链接 */
const goToUrl = () => {
openWindow(props.url);
};
</script>
<template>
<ElAlert
v-if="isDocAlertEnable()"
type="info"
:closable="false"
class="mb-2 rounded"
>
<ElLink type="primary" @click="goToUrl">
{{ title }}文档地址{{ url }}
</ElLink>
</ElAlert>
</template>

View File

@ -0,0 +1 @@
export { default as DocAlert } from './doc-alert.vue';

View File

@ -0,0 +1,256 @@
<script lang="ts" setup>
import type {
UploadFile,
UploadProgressEvent,
UploadRequestOptions,
} from 'element-plus';
import type { AxiosResponse } from '@vben/request';
import type { CustomUploadFile } from './typing';
import type { AxiosProgressEvent } from '#/api/infra/file';
import { ref, toRefs, watch } from 'vue';
import { CloudUpload } from '@vben/icons';
import { $t } from '@vben/locales';
import { isFunction, isObject, isString } from '@vben/utils';
import { ElButton, ElMessage, ElUpload } from 'element-plus';
import { checkFileType } from './helper';
import { UploadResultStatus } from './typing';
import { useUpload, useUploadType } from './use-upload';
defineOptions({ name: 'FileUpload', inheritAttrs: false });
const props = withDefaults(
defineProps<{
//
accept?: string[];
api?: (
file: File,
onUploadProgress?: AxiosProgressEvent,
) => Promise<AxiosResponse<any>>;
//
directory?: string;
disabled?: boolean;
helpText?: string;
// Infinity
maxNumber?: number;
// MB
maxSize?: number;
//
multiple?: boolean;
// support xxx.xxx.xx
resultField?: string;
//
showDescription?: boolean;
value?: string | string[];
}>(),
{
value: () => [],
directory: undefined,
disabled: false,
helpText: '',
maxSize: 2,
maxNumber: 1,
accept: () => [],
multiple: false,
api: undefined,
resultField: '',
showDescription: false,
},
);
const emit = defineEmits(['change', 'update:value', 'delete']);
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
const isInnerOperate = ref<boolean>(false);
const { getStringAccept } = useUploadType({
acceptRef: accept,
helpTextRef: helpText,
maxNumberRef: maxNumber,
maxSizeRef: maxSize,
});
const fileList = ref<CustomUploadFile[]>([]);
const isLtMsg = ref<boolean>(true); //
const isActMsg = ref<boolean>(true); //
const isFirstRender = ref<boolean>(true); //
watch(
() => props.value,
(v) => {
if (isInnerOperate.value) {
isInnerOperate.value = false;
return;
}
let value: string[] = [];
if (v) {
if (Array.isArray(v)) {
value = v;
} else {
value.push(v);
}
fileList.value = value
.map((item, i) => {
if (item && isString(item)) {
return {
uid: -i,
name: item.slice(Math.max(0, item.lastIndexOf('/') + 1)),
status: UploadResultStatus.DONE,
url: item,
} as CustomUploadFile;
} else if (item && isObject(item)) {
const file = item as Record<string, any>;
return {
uid: file.uid || -i,
name: file.name || '',
status: UploadResultStatus.DONE,
url: file.url,
response: file.response,
percentage: file.percentage,
size: file.size,
} as CustomUploadFile;
}
return null;
})
.filter(Boolean) as CustomUploadFile[];
}
if (!isFirstRender.value) {
emit('change', value);
isFirstRender.value = false;
}
},
{
immediate: true,
deep: true,
},
);
const handleRemove = async (file: UploadFile) => {
if (fileList.value) {
const index = fileList.value.findIndex((item) => item.uid === file.uid);
index !== -1 && fileList.value.splice(index, 1);
const value = getValue();
isInnerOperate.value = true;
emit('update:value', value);
emit('change', value);
emit('delete', file);
}
};
const beforeUpload = async (file: File) => {
const { maxSize, accept } = props;
const isAct = checkFileType(file, accept);
if (!isAct) {
ElMessage.error($t('ui.upload.acceptUpload', [accept]));
isActMsg.value = false;
//
setTimeout(() => (isActMsg.value = true), 1000);
return false;
}
const isLt = file.size / 1024 / 1024 > maxSize;
if (isLt) {
ElMessage.error($t('ui.upload.maxSizeMultiple', [maxSize]));
isLtMsg.value = false;
//
setTimeout(() => (isLtMsg.value = true), 1000);
return false;
}
return true;
};
async function customRequest(options: UploadRequestOptions) {
let { api } = props;
if (!api || !isFunction(api)) {
api = useUpload(props.directory).httpRequest;
}
try {
//
const progressEvent: AxiosProgressEvent = (e) => {
const percent = Math.trunc((e.loaded / e.total!) * 100);
const progressEvent: UploadProgressEvent = {
percent,
total: e.total || 0,
loaded: e.loaded || 0,
lengthComputable: true,
target: e.target as EventTarget,
bubbles: false,
cancelBubble: false,
cancelable: false,
composed: false,
currentTarget: e.target as EventTarget,
defaultPrevented: false,
eventPhase: 0,
isTrusted: true,
returnValue: true,
srcElement: e.target as EventTarget,
timeStamp: Date.now(),
type: 'progress',
composedPath: () => [],
initEvent: () => {},
preventDefault: () => {},
stopImmediatePropagation: () => {},
stopPropagation: () => {},
};
options.onProgress!(progressEvent);
};
const res = await api?.(options.file, progressEvent);
options.onSuccess!(res);
ElMessage.success($t('ui.upload.uploadSuccess'));
//
const value = getValue();
isInnerOperate.value = true;
emit('update:value', value);
emit('change', value);
} catch (error: any) {
console.error(error);
options.onError!(error);
}
}
function getValue() {
const list = (fileList.value || [])
.filter((item) => item?.status === UploadResultStatus.DONE)
.map((item: any) => {
if (item?.response && props?.resultField) {
return item?.response;
}
return item?.url || item?.response?.url || item?.response;
});
// add by String
if (props.maxNumber === 1) {
return list.length > 0 ? list[0] : '';
}
return list;
}
</script>
<template>
<div>
<ElUpload
v-bind="$attrs"
v-model:file-list="fileList"
:accept="getStringAccept"
:before-upload="beforeUpload"
:http-request="customRequest"
:disabled="disabled"
:limit="maxNumber"
:multiple="multiple"
list-type="text"
:on-remove="handleRemove"
>
<div v-if="fileList && fileList.length < maxNumber">
<ElButton>
<CloudUpload />
{{ $t('ui.upload.upload') }}
</ElButton>
</div>
<div v-if="showDescription" class="mt-2 text-xs text-gray-500">
{{ getStringAccept }}
</div>
</ElUpload>
</div>
</template>

View File

@ -0,0 +1,20 @@
export function checkFileType(file: File, accepts: string[]) {
if (!accepts || accepts.length === 0) {
return true;
}
const newTypes = accepts.join('|');
const reg = new RegExp(`${String.raw`\.(` + newTypes})$`, 'i');
return reg.test(file.name);
}
/**
*
*/
export const defaultImageAccepts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
export function checkImgType(
file: File,
accepts: string[] = defaultImageAccepts,
) {
return checkFileType(file, accepts);
}

View File

@ -0,0 +1,265 @@
<script lang="ts" setup>
import type {
UploadFile,
UploadProgressEvent,
UploadRequestOptions,
} from 'element-plus';
import type { AxiosResponse } from '@vben/request';
import type { UploadListType } from './typing';
import type { AxiosProgressEvent } from '#/api/infra/file';
import { ref, toRefs, watch } from 'vue';
import { CloudUpload } from '@vben/icons';
import { $t } from '@vben/locales';
import { isFunction, isObject, isString } from '@vben/utils';
import { ElMessage, ElUpload } from 'element-plus';
import { checkImgType, defaultImageAccepts } from './helper';
import { UploadResultStatus } from './typing';
import { useUpload, useUploadType } from './use-upload';
defineOptions({ name: 'ImageUpload', inheritAttrs: false });
const props = withDefaults(
defineProps<{
//
accept?: string[];
api?: (
file: File,
onUploadProgress?: AxiosProgressEvent,
) => Promise<AxiosResponse<any>>;
//
directory?: string;
disabled?: boolean;
helpText?: string;
listType?: UploadListType;
// Infinity
maxNumber?: number;
// MB
maxSize?: number;
//
multiple?: boolean;
// support xxx.xxx.xx
resultField?: string;
//
showDescription?: boolean;
value?: string | string[];
}>(),
{
value: () => [],
directory: undefined,
disabled: false,
listType: 'picture-card',
helpText: '',
maxSize: 2,
maxNumber: 1,
accept: () => defaultImageAccepts,
multiple: false,
api: undefined,
resultField: '',
showDescription: true,
},
);
const emit = defineEmits(['change', 'update:value', 'delete']);
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
const isInnerOperate = ref<boolean>(false);
const { getStringAccept } = useUploadType({
acceptRef: accept,
helpTextRef: helpText,
maxNumberRef: maxNumber,
maxSizeRef: maxSize,
});
const fileList = ref<UploadFile[]>([]);
const isLtMsg = ref<boolean>(true); //
const isActMsg = ref<boolean>(true); //
const isFirstRender = ref<boolean>(true); //
watch(
() => props.value,
async (v) => {
if (isInnerOperate.value) {
isInnerOperate.value = false;
return;
}
let value: string | string[] = [];
if (v) {
if (Array.isArray(v)) {
value = v;
} else {
value.push(v);
}
fileList.value = value
.map((item, i) => {
if (item && isString(item)) {
return {
uid: -i,
name: item.slice(Math.max(0, item.lastIndexOf('/') + 1)),
status: UploadResultStatus.DONE,
url: item,
} as UploadFile;
} else if (item && isObject(item)) {
const file = item as Record<string, any>;
return {
uid: file.uid || -i,
name: file.name || '',
status: UploadResultStatus.DONE,
url: file.url,
} as UploadFile;
}
return null;
})
.filter(Boolean) as UploadFile[];
}
if (!isFirstRender.value) {
emit('change', value);
isFirstRender.value = false;
}
},
{
immediate: true,
deep: true,
},
);
function getBase64<T extends ArrayBuffer | null | string>(file: File) {
return new Promise<T>((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.addEventListener('load', () => {
resolve(reader.result as T);
});
reader.addEventListener('error', (error) => reject(error));
});
}
const handlePreview = async (file: UploadFile) => {
if (!file.url) {
const preview = await getBase64<string>(file.raw!);
window.open(preview || '');
return;
}
window.open(file.url);
};
const handleRemove = async (file: UploadFile) => {
if (fileList.value) {
const index = fileList.value.findIndex((item) => item.uid === file.uid);
index !== -1 && fileList.value.splice(index, 1);
const value = getValue();
isInnerOperate.value = true;
emit('update:value', value);
emit('change', value);
emit('delete', file);
}
};
const beforeUpload = async (file: File) => {
const { maxSize, accept } = props;
const isAct = checkImgType(file, accept);
if (!isAct) {
ElMessage.error($t('ui.upload.acceptUpload', [accept]));
isActMsg.value = false;
//
setTimeout(() => (isActMsg.value = true), 1000);
return false;
}
const isLt = file.size / 1024 / 1024 > maxSize;
if (isLt) {
ElMessage.error($t('ui.upload.maxSizeMultiple', [maxSize]));
isLtMsg.value = false;
//
setTimeout(() => (isLtMsg.value = true), 1000);
return false;
}
return true;
};
async function customRequest(options: UploadRequestOptions) {
let { api } = props;
if (!api || !isFunction(api)) {
api = useUpload(props.directory).httpRequest;
}
try {
//
const progressEvent: AxiosProgressEvent = (e) => {
const percent = Math.trunc((e.loaded / e.total!) * 100);
options.onProgress!({
percent,
total: e.total || 0,
loaded: e.loaded || 0,
lengthComputable: true,
} as unknown as UploadProgressEvent);
};
const res = await api?.(options.file, progressEvent);
options.onSuccess!(res);
ElMessage.success($t('ui.upload.uploadSuccess'));
//
const value = getValue();
isInnerOperate.value = true;
emit('update:value', value);
emit('change', value);
} catch (error: any) {
console.error(error);
options.onError!(error);
}
}
function getValue() {
const list = (fileList.value || [])
.filter((item) => item?.status === UploadResultStatus.DONE)
.map((item: any) => {
if (item?.response && props?.resultField) {
return item?.response;
}
return item?.url || item?.response?.url || item?.response;
});
// add by String
if (props.maxNumber === 1) {
return list.length > 0 ? list[0] : '';
}
return list;
}
</script>
<template>
<div>
<ElUpload
v-bind="$attrs"
v-model:file-list="fileList"
:accept="getStringAccept"
:before-upload="beforeUpload"
:http-request="customRequest"
:disabled="disabled"
:list-type="listType"
:limit="maxNumber"
:multiple="multiple"
:on-preview="handlePreview"
:on-remove="handleRemove"
>
<div
v-if="fileList && fileList.length < maxNumber"
class="flex flex-col items-center justify-center"
>
<CloudUpload />
<div class="mt-2">{{ $t('ui.upload.imgUpload') }}</div>
</div>
</ElUpload>
<div v-if="showDescription" class="mt-2 text-xs text-gray-500">
{{ getStringAccept }}
</div>
</div>
</template>
<style>
.ant-upload-select-picture-card {
@apply flex items-center justify-center;
}
</style>

View File

@ -0,0 +1,2 @@
export { default as FileUpload } from './file-upload.vue';
export { default as ImageUpload } from './image-upload.vue';

View File

@ -0,0 +1,46 @@
import type { UploadStatus } from 'element-plus';
export type UploadListType = 'picture' | 'picture-card' | 'text';
export type UploadStatus = 'error' | 'removed' | 'success' | 'uploading';
export enum UploadResultStatus {
DONE = 'success',
ERROR = 'error',
REMOVED = 'removed',
SUCCESS = 'success',
UPLOADING = 'uploading',
}
export interface CustomUploadFile {
uid: number;
name: string;
status: UploadStatus;
url?: string;
response?: any;
percentage?: number;
size?: number;
raw?: File;
}
export function convertToUploadStatus(
status: UploadResultStatus,
): UploadStatus {
switch (status) {
case UploadResultStatus.DONE: {
return 'success';
}
case UploadResultStatus.ERROR: {
return 'error';
}
case UploadResultStatus.REMOVED: {
return 'removed';
}
case UploadResultStatus.UPLOADING: {
return 'uploading';
}
default: {
return 'success';
}
}
}

View File

@ -0,0 +1,158 @@
import type { Ref } from 'vue';
import type { AxiosProgressEvent, InfraFileApi } from '#/api/infra/file';
import { computed, unref } from 'vue';
import { useAppConfig } from '@vben/hooks';
import { $t } from '@vben/locales';
// import CryptoJS from 'crypto-js';
import { createFile, getFilePresignedUrl, uploadFile } from '#/api/infra/file';
import { baseRequestClient } from '#/api/request';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
/**
*
*/
enum UPLOAD_TYPE {
// 客户端直接上传只支持S3服务
CLIENT = 'client',
// 客户端发送到后端上传
SERVER = 'server',
}
export function useUploadType({
acceptRef,
helpTextRef,
maxNumberRef,
maxSizeRef,
}: {
acceptRef: Ref<string[]>;
helpTextRef: Ref<string>;
maxNumberRef: Ref<number>;
maxSizeRef: Ref<number>;
}) {
// 文件类型限制
const getAccept = computed(() => {
const accept = unref(acceptRef);
if (accept && accept.length > 0) {
return accept;
}
return [];
});
const getStringAccept = computed(() => {
return unref(getAccept)
.map((item) => {
return item.indexOf('/') > 0 || item.startsWith('.')
? item
: `.${item}`;
})
.join(',');
});
// 支持jpg、jpeg、png格式不超过2M最多可选择10张图片
const getHelpText = computed(() => {
const helpText = unref(helpTextRef);
if (helpText) {
return helpText;
}
const helpTexts: string[] = [];
const accept = unref(acceptRef);
if (accept.length > 0) {
helpTexts.push($t('ui.upload.accept', [accept.join(',')]));
}
const maxSize = unref(maxSizeRef);
if (maxSize) {
helpTexts.push($t('ui.upload.maxSize', [maxSize]));
}
const maxNumber = unref(maxNumberRef);
if (maxNumber && maxNumber !== Infinity) {
helpTexts.push($t('ui.upload.maxNumber', [maxNumber]));
}
return helpTexts.join('');
});
return { getAccept, getStringAccept, getHelpText };
}
// TODO @芋艿:目前保持和 admin-vue3 一致,后续可能重构
export const useUpload = (directory?: string) => {
// 后端上传地址
const uploadUrl = getUploadUrl();
// 是否使用前端直连上传
const isClientUpload =
UPLOAD_TYPE.CLIENT === import.meta.env.VITE_UPLOAD_TYPE;
// 重写ElUpload上传方法
const httpRequest = async (
file: File,
onUploadProgress?: AxiosProgressEvent,
) => {
// 模式一:前端上传
if (isClientUpload) {
// 1.1 生成文件名称
const fileName = await generateFileName(file);
// 1.2 获取文件预签名地址
const presignedInfo = await getFilePresignedUrl(fileName, directory);
// 1.3 上传文件
return baseRequestClient
.put(presignedInfo.uploadUrl, file, {
headers: {
'Content-Type': file.type,
},
})
.then(() => {
// 1.4. 记录文件信息到后端(异步)
createFile0(presignedInfo, file);
// 通知成功,数据格式保持与后端上传的返回结果一致
return { url: presignedInfo.url };
});
} else {
// 模式二:后端上传
return uploadFile({ file, directory }, onUploadProgress);
}
};
return {
uploadUrl,
httpRequest,
};
};
/**
* URL
*/
export const getUploadUrl = (): string => {
return `${apiURL}/infra/file/upload`;
};
/**
*
*
* @param vo
* @param file
*/
function createFile0(vo: InfraFileApi.FilePresignedUrlRespVO, file: File) {
const fileVO = {
configId: vo.configId,
url: vo.url,
path: vo.path,
name: file.name,
type: file.type,
size: file.size,
};
createFile(fileVO);
return fileVO;
}
/**
*
*
* @param file
*/
async function generateFileName(file: File) {
return file.name;
}

View File

@ -10,5 +10,23 @@
"title": "Dashboard",
"analytics": "Analytics",
"workspace": "Workspace"
},
"action": {
"action": "Action",
"add": "Add",
"edit": "Edit",
"delete": "Delete",
"save": "Save",
"import": "Import",
"export": "Export",
"submit": "Submit",
"cancel": "Cancel",
"confirm": "Confirm",
"reset": "Reset",
"search": "Search"
},
"tenant": {
"placeholder": "Please select tenant",
"success": "Switch tenant success"
}
}

View File

@ -0,0 +1,14 @@
{
"rangePicker": {
"today": "Today",
"last7Days": "Last 7 Days",
"last30Days": "Last 30 Days",
"yesterday": "Yesterday",
"thisWeek": "This Week",
"thisMonth": "This Month",
"lastWeek": "Last Week",
"lastMonth": "Last Month",
"beginTime": "Begin Time",
"endTime": "End Time"
}
}

View File

@ -10,5 +10,23 @@
"title": "概览",
"analytics": "分析页",
"workspace": "工作台"
},
"action": {
"action": "操作",
"add": "新增",
"edit": "编辑",
"delete": "删除",
"save": "保存",
"import": "导入",
"export": "导出",
"submit": "提交",
"cancel": "取消",
"confirm": "确认",
"reset": "重置",
"search": "搜索"
},
"tenant": {
"placeholder": "请选择租户",
"success": "切换租户成功"
}
}

View File

@ -0,0 +1,14 @@
{
"rangePicker": {
"today": "今天",
"last7Days": "最近 7 天",
"last30Days": "最近 30 天",
"yesterday": "昨天",
"thisWeek": "本周",
"thisMonth": "本月",
"lastWeek": "上周",
"lastMonth": "上月",
"beginTime": "开始时间",
"endTime": "结束时间"
}
}

View File

@ -8,6 +8,18 @@ import { defineOverridesPreferences } from '@vben/preferences';
export const overridesPreferences = defineOverridesPreferences({
// overrides
app: {
/** 后端路由模式 */
accessMode: 'backend',
name: import.meta.env.VITE_APP_TITLE,
enableRefreshToken: true,
},
footer: {
/** 默认关闭 footer 页脚,因为有一定遮挡 */
enable: false,
fixed: false,
},
copyright: {
companyName: import.meta.env.VITE_APP_TITLE,
companySiteLink: 'https://gitee.com/yudaocode/yudao-ui-admin-vben',
},
});

View File

@ -1,21 +1,21 @@
import type {
AppRouteRecordRaw,
ComponentRecordType,
GenerateMenuAndRoutesOptions,
} from '@vben/types';
import { generateAccessible } from '@vben/access';
import { preferences } from '@vben/preferences';
import { useAccessStore } from '@vben/stores';
import { convertServerMenuToRouteRecordStringComponent } from '@vben/utils';
import { ElMessage } from 'element-plus';
import { getAllMenusApi } from '#/api';
import { BasicLayout, IFrameView } from '#/layouts';
import { $t } from '#/locales';
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
const accessStore = useAccessStore();
const layoutMap: ComponentRecordType = {
BasicLayout,
@ -25,11 +25,10 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
return await generateAccessible(preferences.app.accessMode, {
...options,
fetchMenuListAsync: async () => {
ElMessage({
duration: 1500,
message: `${$t('common.loadingMenu')}...`,
});
return await getAllMenusApi();
// 由于 yudao 通过 accessStore 读取,所以不在进行 message.loading 提示
// 补充说明accessStore.accessMenus 一开始是 AppRouteRecordRaw 类型(后端加载),后面被赋值成 MenuRecordRaw 类型(前端转换)
const accessMenus = accessStore.accessMenus as AppRouteRecordRaw[];
return convertServerMenuToRouteRecordStringComponent(accessMenus);
},
// 可以指定没有权限跳转403页面
forbiddenComponent,

View File

@ -1,12 +1,16 @@
import type { Router } from 'vue-router';
import { LOGIN_PATH } from '@vben/constants';
import { $t } from '@vben/locales';
import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { startProgress, stopProgress } from '@vben/utils';
import { ElMessage } from 'element-plus';
import { getSimpleDictDataList } from '#/api/system/dict/data';
import { accessRoutes, coreRouteNames } from '#/router/routes';
import { useAuthStore } from '#/store';
import { useAuthStore, useDictStore } from '#/store';
import { generateAccess } from './access';
@ -49,6 +53,7 @@ function setupAccessGuard(router: Router) {
const accessStore = useAccessStore();
const userStore = useUserStore();
const authStore = useAuthStore();
const dictStore = useDictStore();
// 基本路由,这些路由不需要进入权限拦截
if (coreRouteNames.includes(to.name as string)) {
@ -90,10 +95,29 @@ function setupAccessGuard(router: Router) {
return true;
}
// 加载字典数据(不阻塞加载)
dictStore.setDictCacheByApi(getSimpleDictDataList);
// 生成路由表
// 当前登录用户拥有的角色标识列表
const userInfo = userStore.userInfo || (await authStore.fetchUserInfo());
const userRoles = userInfo.roles ?? [];
let userInfo = userStore.userInfo;
if (!userInfo) {
// add by 芋艿:由于 yudao 是 fetchUserInfo 统一加载用户 + 权限信息,所以将 fetchMenuListAsync
const message = ElMessage({
message: `${$t('common.loadingMenu')}...`,
type: 'success',
plain: true,
});
try {
const authPermissionInfo = await authStore.fetchUserInfo();
if (authPermissionInfo) {
userInfo = authPermissionInfo.user;
}
} finally {
message.close();
}
}
const userRoles = userStore.userRoles ?? [];
// 生成菜单和路由
const { accessibleMenus, accessibleRoutes } = await generateAccess({
@ -107,9 +131,10 @@ function setupAccessGuard(router: Router) {
accessStore.setAccessMenus(accessibleMenus);
accessStore.setAccessRoutes(accessibleRoutes);
accessStore.setIsAccessChecked(true);
userStore.setUserRoles(userRoles);
const redirectPath = (from.query.redirect ??
(to.path === preferences.app.defaultHomePath
? userInfo.homePath || preferences.app.defaultHomePath
? userInfo?.homePath || preferences.app.defaultHomePath
: to.fullPath)) as string;
return {

View File

@ -8,6 +8,7 @@ import { resetStaticRoutes } from '@vben/utils';
import { createRouterGuard } from './guard';
import { routes } from './routes';
import { setupBaiduTongJi } from './tongji';
/**
* @zh_CN vue-router
@ -33,5 +34,7 @@ const resetRoutes = () => resetStaticRoutes(router, routes);
// 创建路由守卫
createRouterGuard(router);
// 设置百度统计
setupBaiduTongJi(router);
export { resetRoutes, router };

View File

@ -90,6 +90,23 @@ const coreRoutes: RouteRecordRaw[] = [
title: $t('page.auth.register'),
},
},
{
name: 'SocialLogin',
path: 'social-login',
component: () =>
import('#/views/_core/authentication/social-login.vue'),
meta: {
title: $t('page.auth.login'),
},
},
{
name: 'SSOLogin',
path: 'sso-login',
component: () => import('#/views/_core/authentication/sso-login.vue'),
meta: {
title: $t('page.auth.login'),
},
},
],
},
];

View File

@ -34,4 +34,14 @@ const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
/** 有权限校验的路由列表,包含动态路由和静态路由 */
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
export { accessRoutes, coreRouteNames, routes };
// add by 芋艿from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/router/routes/index.ts#L38-L45
const componentKeys: string[] = Object.keys(
import.meta.glob('../../views/**/*.vue'),
)
.filter((item) => !item.includes('/modules/'))
.map((v) => {
const path = v.replace('../../views/', '/');
return path.endsWith('.vue') ? path.slice(0, -4) : path;
});
export { accessRoutes, componentKeys, coreRouteNames, routes };

View File

@ -12,6 +12,15 @@ const routes: RouteRecordRaw[] = [
name: 'Dashboard',
path: '/dashboard',
children: [
{
name: 'Workspace',
path: '/workspace',
component: () => import('#/views/dashboard/workspace/index.vue'),
meta: {
icon: 'carbon:workspace',
title: $t('page.dashboard.workspace'),
},
},
{
name: 'Analytics',
path: '/analytics',
@ -22,17 +31,18 @@ const routes: RouteRecordRaw[] = [
title: $t('page.dashboard.analytics'),
},
},
{
name: 'Workspace',
path: '/workspace',
component: () => import('#/views/dashboard/workspace/index.vue'),
meta: {
icon: 'carbon:workspace',
title: $t('page.dashboard.workspace'),
},
},
],
},
{
name: 'Profile',
path: '/profile',
component: () => import('#/views/_core/profile/index.vue'),
meta: {
icon: 'ant-design:profile-outlined',
title: $t('ui.widgets.profile'),
hideInMenu: true,
},
},
];
export default routes;

View File

@ -1,36 +0,0 @@
import type { RouteRecordRaw } from 'vue-router';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
meta: {
icon: 'ic:baseline-view-in-ar',
keepAlive: true,
order: 1000,
title: $t('demos.title'),
},
name: 'Demos',
path: '/demos',
children: [
{
meta: {
title: $t('demos.elementPlus'),
},
name: 'NaiveDemos',
path: '/demos/element',
component: () => import('#/views/demos/element/index.vue'),
},
{
meta: {
title: $t('demos.form'),
},
name: 'BasicForm',
path: '/demos/form',
component: () => import('#/views/demos/form/basic.vue'),
},
],
},
];
export default routes;

View File

@ -1,82 +1,81 @@
import type { RouteRecordRaw } from 'vue-router';
import {
VBEN_ANT_PREVIEW_URL,
VBEN_DOC_URL,
VBEN_GITHUB_URL,
VBEN_LOGO_URL,
VBEN_NAIVE_PREVIEW_URL,
} from '@vben/constants';
import { SvgAntdvLogoIcon } from '@vben/icons';
import { IFrameView } from '#/layouts';
import { $t } from '#/locales';
// import {
// VBEN_DOC_URL,
// VBEN_ELE_PREVIEW_URL,
// VBEN_GITHUB_URL,
// VBEN_LOGO_URL,
// VBEN_NAIVE_PREVIEW_URL,
// } from '@vben/constants';
//
// import { IFrameView } from '#/layouts';
// import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
meta: {
badgeType: 'dot',
icon: VBEN_LOGO_URL,
order: 9998,
title: $t('demos.vben.title'),
},
name: 'VbenProject',
path: '/vben-admin',
children: [
{
name: 'VbenDocument',
path: '/vben-admin/document',
component: IFrameView,
meta: {
icon: 'lucide:book-open-text',
link: VBEN_DOC_URL,
title: $t('demos.vben.document'),
},
},
{
name: 'VbenGithub',
path: '/vben-admin/github',
component: IFrameView,
meta: {
icon: 'mdi:github',
link: VBEN_GITHUB_URL,
title: 'Github',
},
},
{
name: 'VbenNaive',
path: '/vben-admin/naive',
component: IFrameView,
meta: {
badgeType: 'dot',
icon: 'logos:naiveui',
link: VBEN_NAIVE_PREVIEW_URL,
title: $t('demos.vben.naive-ui'),
},
},
{
name: 'VbenAntd',
path: '/vben-admin/antd',
component: IFrameView,
meta: {
badgeType: 'dot',
icon: SvgAntdvLogoIcon,
link: VBEN_ANT_PREVIEW_URL,
title: $t('demos.vben.antdv'),
},
},
],
},
{
name: 'VbenAbout',
path: '/vben-admin/about',
component: () => import('#/views/_core/about/index.vue'),
meta: {
icon: 'lucide:copyright',
title: $t('demos.vben.about'),
order: 9999,
},
},
// {
// meta: {
// badgeType: 'dot',
// icon: VBEN_LOGO_URL,
// order: 9998,
// title: $t('demos.vben.title'),
// },
// name: 'VbenProject',
// path: '/vben-admin',
// children: [
// {
// name: 'VbenDocument',
// path: '/vben-admin/document',
// component: IFrameView,
// meta: {
// icon: 'lucide:book-open-text',
// link: VBEN_DOC_URL,
// title: $t('demos.vben.document'),
// },
// },
// {
// name: 'VbenGithub',
// path: '/vben-admin/github',
// component: IFrameView,
// meta: {
// icon: 'mdi:github',
// link: VBEN_GITHUB_URL,
// title: 'Github',
// },
// },
// {
// name: 'VbenNaive',
// path: '/vben-admin/naive',
// component: IFrameView,
// meta: {
// badgeType: 'dot',
// icon: 'logos:naiveui',
// link: VBEN_NAIVE_PREVIEW_URL,
// title: $t('demos.vben.naive-ui'),
// },
// },
// {
// name: 'VbenElementPlus',
// path: '/vben-admin/ele',
// component: IFrameView,
// meta: {
// badgeType: 'dot',
// icon: 'logos:element',
// link: VBEN_ELE_PREVIEW_URL,
// title: $t('demos.vben.element-plus'),
// },
// },
// ],
// },
// {
// name: 'VbenAbout',
// path: '/vben-admin/about',
// component: () => import('#/views/_core/about/index.vue'),
// meta: {
// icon: 'lucide:copyright',
// title: $t('demos.vben.about'),
// order: 9999,
// },
// },
];
export default routes;
export default routes; // update by 芋艿:不展示

View File

@ -0,0 +1,30 @@
import type { Router } from 'vue-router';
declare global {
interface Window {
_hmt: any[];
}
}
const HM_ID = import.meta.env.VITE_APP_BAIDU_CODE;
/**
*
* @param router
*/
function setupBaiduTongJi(router: Router) {
// 如果没有配置百度统计的 ID则不进行设置
if (!HM_ID) {
return;
}
// _hmt用于 router push
window._hmt = window._hmt || [];
router.afterEach((to) => {
// 添加到 _hmt 中
window._hmt.push(['_trackPageview', to.fullPath]);
});
}
export { setupBaiduTongJi };

View File

@ -1,4 +1,6 @@
import type { Recordable, UserInfo } from '@vben/types';
import type { AuthPermissionInfo, Recordable, UserInfo } from '@vben/types';
import type { AuthApi } from '#/api';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
@ -10,7 +12,14 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
import { ElNotification } from 'element-plus';
import { defineStore } from 'pinia';
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
import {
getAuthPermissionInfoApi,
loginApi,
logoutApi,
register,
smsLogin,
socialLogin,
} from '#/api';
import { $t } from '#/locales';
export const useAuthStore = defineStore('auth', () => {
@ -23,9 +32,12 @@ export const useAuthStore = defineStore('auth', () => {
/**
*
* Asynchronously handle the login process
* @param type
* @param params
* @param onSuccess
*/
async function authLogin(
type: 'mobile' | 'register' | 'social' | 'username',
params: Recordable<any>,
onSuccess?: () => Promise<void> | void,
) {
@ -33,23 +45,30 @@ export const useAuthStore = defineStore('auth', () => {
let userInfo: null | UserInfo = null;
try {
loginLoading.value = true;
const { accessToken } = await loginApi(params);
const { accessToken, refreshToken } =
type === 'mobile'
? await smsLogin(params as AuthApi.SmsLoginParams)
: type === 'register'
? await register(params as AuthApi.RegisterParams)
: // eslint-disable-next-line unicorn/no-nested-ternary
type === 'social'
? await socialLogin(params as AuthApi.SocialLoginParams)
: await loginApi(params);
// 如果成功获取到 accessToken
if (accessToken) {
// 将 accessToken 存储到 accessStore 中
accessStore.setAccessToken(accessToken);
accessStore.setRefreshToken(refreshToken);
// 获取用户信息并存储到 accessStore 中
const [fetchUserInfoResult, accessCodes] = await Promise.all([
fetchUserInfo(),
getAccessCodesApi(),
]);
// 获取用户信息并存储到 userStore、accessStore 中
// TODO @芋艿:清理掉 accessCodes 相关的逻辑
// const [fetchUserInfoResult, accessCodes] = await Promise.all([
// fetchUserInfo(),
// // getAccessCodesApi(),
// ]);
const fetchUserInfoResult = await fetchUserInfo();
userInfo = fetchUserInfoResult;
userStore.setUserInfo(userInfo);
accessStore.setAccessCodes(accessCodes);
userInfo = fetchUserInfoResult.user;
if (accessStore.loginExpired) {
accessStore.setLoginExpired(false);
@ -61,11 +80,11 @@ export const useAuthStore = defineStore('auth', () => {
);
}
if (userInfo?.realName) {
ElNotification({
message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,
if (userInfo?.nickname) {
ElNotification.success({
message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.nickname}`,
duration: 3,
title: $t('authentication.loginSuccess'),
type: 'success',
});
}
}
@ -80,7 +99,10 @@ export const useAuthStore = defineStore('auth', () => {
async function logout(redirect: boolean = true) {
try {
await logoutApi();
const accessToken = accessStore.accessToken as string;
if (accessToken) {
await logoutApi(accessToken);
}
} catch {
// 不做任何处理
}
@ -99,10 +121,16 @@ export const useAuthStore = defineStore('auth', () => {
}
async function fetchUserInfo() {
let userInfo: null | UserInfo = null;
userInfo = await getUserInfoApi();
userStore.setUserInfo(userInfo);
return userInfo;
// 加载
let authPermissionInfo: AuthPermissionInfo | null;
authPermissionInfo = await getAuthPermissionInfoApi();
// userStore
userStore.setUserInfo(authPermissionInfo.user);
userStore.setUserRoles(authPermissionInfo.roles);
// accessStore
accessStore.setAccessMenus(authPermissionInfo.menus);
accessStore.setAccessCodes(authPermissionInfo.permissions);
return authPermissionInfo;
}
function $reset() {

View File

@ -0,0 +1,74 @@
import { acceptHMRUpdate, defineStore } from 'pinia';
export interface DictItem {
colorType?: string;
cssClass?: string;
label: string;
value: string;
}
export type Dict = Record<string, DictItem[]>;
interface DictState {
dictCache: Dict;
}
// TODO @芋艿:可以共享么?
export const useDictStore = defineStore('dict', {
actions: {
getDictData(dictType: string, value: any) {
const dict = this.dictCache[dictType];
if (!dict) {
return undefined;
}
return (
dict.find((d) => d.value === value || d.value === value.toString()) ??
undefined
);
},
getDictOptions(dictType: string) {
const dictOptions = this.dictCache[dictType];
if (!dictOptions) {
return [];
}
return dictOptions;
},
setDictCache(dicts: Dict) {
this.dictCache = dicts;
},
setDictCacheByApi(
api: (params: Record<string, any>) => Promise<Record<string, any>[]>,
params: Record<string, any> = {},
labelField: string = 'label',
valueField: string = 'value',
) {
api(params).then((dicts) => {
const dictCacheData: Dict = {};
dicts.forEach((dict) => {
dictCacheData[dict.dictType] = dicts
.filter((d) => d.dictType === dict.dictType)
.map((d) => ({
colorType: d.colorType,
cssClass: d.cssClass,
label: d[labelField],
value: d[valueField],
}));
});
this.setDictCache(dictCacheData);
});
},
},
persist: {
// 持久化
pick: ['dictCache'],
},
state: (): DictState => ({
dictCache: {},
}),
});
// 解决热更新问题
const hot = import.meta.hot;
if (hot) {
hot.accept(acceptHMRUpdate(useDictStore, hot));
}

View File

@ -1 +1,2 @@
export * from './auth';
export * from './dict';

View File

@ -0,0 +1,636 @@
// todo @芋艿:要不要共享
/**
* Created by
*
*
*/
// ========== COMMON 模块 ==========
// 全局通用状态枚举
export const CommonStatusEnum = {
ENABLE: 0, // 开启
DISABLE: 1, // 禁用
};
// 全局用户类型枚举
export const UserTypeEnum = {
MEMBER: 1, // 会员
ADMIN: 2, // 管理员
};
// ========== SYSTEM 模块 ==========
/**
*
*/
export const SystemMenuTypeEnum = {
DIR: 1, // 目录
MENU: 2, // 菜单
BUTTON: 3, // 按钮
};
/**
*
*/
export const SystemRoleTypeEnum = {
SYSTEM: 1, // 内置角色
CUSTOM: 2, // 自定义角色
};
/**
*
*/
export const SystemDataScopeEnum = {
ALL: 1, // 全部数据权限
DEPT_CUSTOM: 2, // 指定部门数据权限
DEPT_ONLY: 3, // 部门数据权限
DEPT_AND_CHILD: 4, // 部门及以下数据权限
DEPT_SELF: 5, // 仅本人数据权限
};
/**
*
*/
export const SystemUserSocialTypeEnum = {
DINGTALK: {
title: '钉钉',
type: 20,
source: 'dingtalk',
img: 'https://s1.ax1x.com/2022/05/22/OzMDRs.png',
},
WECHAT_ENTERPRISE: {
title: '企业微信',
type: 30,
source: 'wechat_enterprise',
img: 'https://s1.ax1x.com/2022/05/22/OzMrzn.png',
},
};
// ========== INFRA 模块 ==========
/**
*
*/
export const InfraCodegenTemplateTypeEnum = {
CRUD: 1, // 基础 CRUD
TREE: 2, // 树形 CRUD
SUB: 15, // 主子表 CRUD
};
/**
*
*/
export const InfraJobStatusEnum = {
INIT: 0, // 初始化中
NORMAL: 1, // 运行中
STOP: 2, // 暂停运行
};
/**
* API
*/
export const InfraApiErrorLogProcessStatusEnum = {
INIT: 0, // 未处理
DONE: 1, // 已处理
IGNORE: 2, // 已忽略
};
// ========== PAY 模块 ==========
/**
*
*/
export const PayChannelEnum = {
WX_PUB: {
code: 'wx_pub',
name: '微信 JSAPI 支付',
},
WX_LITE: {
code: 'wx_lite',
name: '微信小程序支付',
},
WX_APP: {
code: 'wx_app',
name: '微信 APP 支付',
},
WX_NATIVE: {
code: 'wx_native',
name: '微信 Native 支付',
},
WX_WAP: {
code: 'wx_wap',
name: '微信 WAP 网站支付',
},
WX_BAR: {
code: 'wx_bar',
name: '微信条码支付',
},
ALIPAY_PC: {
code: 'alipay_pc',
name: '支付宝 PC 网站支付',
},
ALIPAY_WAP: {
code: 'alipay_wap',
name: '支付宝 WAP 网站支付',
},
ALIPAY_APP: {
code: 'alipay_app',
name: '支付宝 APP 支付',
},
ALIPAY_QR: {
code: 'alipay_qr',
name: '支付宝扫码支付',
},
ALIPAY_BAR: {
code: 'alipay_bar',
name: '支付宝条码支付',
},
WALLET: {
code: 'wallet',
name: '钱包支付',
},
MOCK: {
code: 'mock',
name: '模拟支付',
},
};
/**
*
*/
export const PayDisplayModeEnum = {
URL: {
mode: 'url',
},
IFRAME: {
mode: 'iframe',
},
FORM: {
mode: 'form',
},
QR_CODE: {
mode: 'qr_code',
},
APP: {
mode: 'app',
},
};
/**
*
*/
export const PayType = {
WECHAT: 'WECHAT',
ALIPAY: 'ALIPAY',
MOCK: 'MOCK',
};
/**
*
*/
export const PayOrderStatusEnum = {
WAITING: {
status: 0,
name: '未支付',
},
SUCCESS: {
status: 10,
name: '已支付',
},
CLOSED: {
status: 20,
name: '未支付',
},
};
// ========== MALL - 商品模块 ==========
/**
* SPU
*/
export const ProductSpuStatusEnum = {
RECYCLE: {
status: -1,
name: '回收站',
},
DISABLE: {
status: 0,
name: '下架',
},
ENABLE: {
status: 1,
name: '上架',
},
};
// ========== MALL - 营销模块 ==========
/**
*
*/
export const CouponTemplateValidityTypeEnum = {
DATE: {
type: 1,
name: '固定日期可用',
},
TERM: {
type: 2,
name: '领取之后可用',
},
};
/**
*
*/
export const CouponTemplateTakeTypeEnum = {
USER: {
type: 1,
name: '直接领取',
},
ADMIN: {
type: 2,
name: '指定发放',
},
REGISTER: {
type: 3,
name: '新人券',
},
};
/**
*
*/
export const PromotionProductScopeEnum = {
ALL: {
scope: 1,
name: '通用劵',
},
SPU: {
scope: 2,
name: '商品劵',
},
CATEGORY: {
scope: 3,
name: '品类劵',
},
};
/**
*
*/
export const PromotionConditionTypeEnum = {
PRICE: {
type: 10,
name: '满 N 元',
},
COUNT: {
type: 20,
name: '满 N 件',
},
};
/**
*
*/
export const PromotionDiscountTypeEnum = {
PRICE: {
type: 1,
name: '满减',
},
PERCENT: {
type: 2,
name: '折扣',
},
};
// ========== MALL - 交易模块 ==========
/**
*
*/
export const BrokerageBindModeEnum = {
ANYTIME: {
mode: 1,
name: '首次绑定',
},
REGISTER: {
mode: 2,
name: '注册绑定',
},
OVERRIDE: {
mode: 3,
name: '覆盖绑定',
},
};
/**
*
*/
export const BrokerageEnabledConditionEnum = {
ALL: {
condition: 1,
name: '人人分销',
},
ADMIN: {
condition: 2,
name: '指定分销',
},
};
/**
*
*/
export const BrokerageRecordBizTypeEnum = {
ORDER: {
type: 1,
name: '获得推广佣金',
},
WITHDRAW: {
type: 2,
name: '提现申请',
},
};
/**
*
*/
export const BrokerageWithdrawStatusEnum = {
AUDITING: {
status: 0,
name: '审核中',
},
AUDIT_SUCCESS: {
status: 10,
name: '审核通过',
},
AUDIT_FAIL: {
status: 20,
name: '审核不通过',
},
WITHDRAW_SUCCESS: {
status: 11,
name: '提现成功',
},
WITHDRAW_FAIL: {
status: 21,
name: '提现失败',
},
};
/**
*
*/
export const BrokerageWithdrawTypeEnum = {
WALLET: {
type: 1,
name: '钱包',
},
BANK: {
type: 2,
name: '银行卡',
},
WECHAT: {
type: 3,
name: '微信',
},
ALIPAY: {
type: 4,
name: '支付宝',
},
};
/**
*
*/
export const DeliveryTypeEnum = {
EXPRESS: {
type: 1,
name: '快递发货',
},
PICK_UP: {
type: 2,
name: '到店自提',
},
};
/**
* -
*/
export const TradeOrderStatusEnum = {
UNPAID: {
status: 0,
name: '待支付',
},
UNDELIVERED: {
status: 10,
name: '待发货',
},
DELIVERED: {
status: 20,
name: '已发货',
},
COMPLETED: {
status: 30,
name: '已完成',
},
CANCELED: {
status: 40,
name: '已取消',
},
};
// ========== ERP - 企业资源计划 ==========
export const ErpBizType = {
PURCHASE_ORDER: 10,
PURCHASE_IN: 11,
PURCHASE_RETURN: 12,
SALE_ORDER: 20,
SALE_OUT: 21,
SALE_RETURN: 22,
};
// ========== BPM 模块 ==========
export const BpmModelType = {
BPMN: 10, // BPMN 设计器
SIMPLE: 20, // 简易设计器
};
export const BpmModelFormType = {
NORMAL: 10, // 流程表单
CUSTOM: 20, // 业务表单
};
export const BpmProcessInstanceStatus = {
NOT_START: -1, // 未开始
RUNNING: 1, // 审批中
APPROVE: 2, // 审批通过
REJECT: 3, // 审批不通过
CANCEL: 4, // 已取消
};
export const BpmAutoApproveType = {
NONE: 0, // 不自动通过
APPROVE_ALL: 1, // 仅审批一次,后续重复的审批节点均自动通过
APPROVE_SEQUENT: 2, // 仅针对连续审批的节点自动通过
};
// 候选人策略枚举 用于审批节点。抄送节点 )
export enum CandidateStrategyEnum {
/**
*
*/
APPROVE_USER_SELECT = 34,
/**
*
*/
DEPT_LEADER = 21,
/**
*
*/
DEPT_MEMBER = 20,
/**
*
*/
EXPRESSION = 60,
/**
*
*/
FORM_DEPT_LEADER = 51,
/**
*
*/
FORM_USER = 50,
/**
*
*/
MULTI_LEVEL_DEPT_LEADER = 23,
/**
*
*/
POST = 22,
/**
*
*/
ROLE = 10,
/**
*
*/
START_USER = 36,
/**
*
*/
START_USER_DEPT_LEADER = 37,
/**
*
*/
START_USER_MULTI_LEVEL_DEPT_LEADER = 38,
/**
*
*/
START_USER_SELECT = 35,
/**
*
*/
USER = 30,
/**
*
*/
USER_GROUP = 40,
}
/**
*
*/
export enum NodeTypeEnum {
/**
*
*/
CHILD_PROCESS_NODE = 20,
/**
* ()
*/
CONDITION_BRANCH_NODE = 51,
/**
*
*/
CONDITION_NODE = 50,
/**
*
*/
COPY_TASK_NODE = 12,
/**
*
*/
DELAY_TIMER_NODE = 14,
/**
*
*/
END_EVENT_NODE = 1,
/**
* ()
*/
INCLUSIVE_BRANCH_NODE = 53,
/**
* ()
*/
PARALLEL_BRANCH_NODE = 52,
/**
*
*/
ROUTER_BRANCH_NODE = 54,
/**
*
*/
START_USER_NODE = 10,
/**
*
*/
TRANSACTOR_NODE = 13,
/**
*
*/
TRIGGER_NODE = 15,
/**
*
*/
USER_TASK_NODE = 11,
}
/**
*
*/
export enum TaskStatusEnum {
/**
*
*/
APPROVE = 2,
/**
*
*/
APPROVING = 7,
/**
*
*/
CANCEL = 4,
/**
*
*/
NOT_START = -1,
/**
*
*/
REJECT = 3,
/**
* 退
*/
RETURN = 5,
/**
*
*/
RUNNING = 1,
/**
*
*/
WAIT = 0,
}

Some files were not shown because too many files have changed in this diff Show More